前言
学习技术特别是工作中经常运用不到的技术也许会让很多人感觉烦闷枯燥,理论如果不结合实践终究会成为过眼云烟。技术没有捷径,需要讲究方法地持续学习。既然是原理,他肯定是一个动作化地一系列行为总和。行为的背后肯定有主体或者实体以及引入的背景。所以个人觉得在面试中,如果面试官问一些原理性东西,最好的方式还是why what how;why-用于解答为什么需要这个东西,what-说明这个东西是什么以及其特点 how-主要是结合what行为化的描述其是如何实现特点的。
摘要
本节是个人学习总结,主要是结合简单的案例demo代码以图文并貌的形式总结下自己学习的JVM工作原理。从为什么引入JVM的java代码如何运行?到JVM运行时候需要用到什么主体-类加载器、字节码执行引擎、JVM的各个运行时候数据区以及JVM在怎样运行我们代码的,类加载器加载的数据会加载存储到哪些数据区,字节码执行引擎的时候是如何结合我们的JVM运行数据去来工作的?以此来加深自己的知识体系。
内容
1、案例引入我们为什么需要JVM.
我们工作中写的代码是如何运行起来呢?我们的.java后缀的代码(源代码)经过打包编译后成为字节码.class(jar/war包),然后启动一个tomcat或者使用java -jar xxx.jar即可开启一个JVM进程来运行我们的java代码。上面就是我们为什么需要JVM的原因。
2、JVM工作原理运行时候需要哪些实体.
我们从工作中写的代码如何运行起来可以知道?JVM运行起来时候是从编译后的.class文件进行加载,然后肯定是加载到内存中,所以这里面就有JVM的运行数据区,运行的代码文件不可能永无止境在加载到内存,肯定会存在内存满的时候,所以就存在垃圾,然后就牵涉到垃圾回收。所以JVM运行时候主要从类加载->在JVM的内存区域运行代码->然后运行的代码进行垃圾回收。
2.1 Java类加载机制
类加载肯定是加载java中的.class文件,但是什么时候才会加载一个类?以及类加载到使用释放的过程?类加载有哪些类加载器以及类加载的时候类加载的原理。
JVM在什么情况下会加载一个类?
当我们通过java -jar命令运行一个JVM进程,此时JVM会先加载我们main方法所在的类,然后通过字节码引擎执行执行main方法,当需要某一个类的时候就会加载某个类。
类从加载到使用经历的过程
当需要使用到一个类的时候,会加载这个类,这个类从加载到使用的过程如下:
加载->校验->准备->解析->初始化->使用->卸载。
加载:类加载器加载需要使用到的类(按需加载)。
校验:校验加载的类是否符合JVM规范,不符合规范的报错,不再进行其他步骤。
准备:给加载进来的类变量分配空间,并设定默认值。引用类型为null,基本类型为对应值的原始值,比如int类型的为0等
解析:将符号引用替换为直接引用
初始化:先初始化类相关的代码比如静态成员变量,静态代码块,然后初始化实例数据,初始化一个类时候,发现父类还没初始化,先初始化父类。
使用:使用对象。
卸载:垃圾回收。
我们看下下面的代码执行步骤:
ReplicaManager
:
/**
* @Author: yexinming
* @Description: 副本管理器
* @Date: 2021/5/29 9:24 上午
*/
public class ReplicaManager extends AbstractReplicaManager{
public static int flushInterval = Configuration.getInt("replica.flush.interval");
public static Map<String,Replica> replicas;
private Integer count;
private int cnt;
public ReplicaManager(){
super(4);
System.out.println("==ReplicaManager constructor 对象成员变量:count 之前=="+this.count);
System.out.println("==ReplicaManager constructor 对象成员变量:cnt 之前=="+this.cnt);
System.out.println("==ReplicaManager constructor 构造器方法执行==");
this.count = 5;
System.out.println("==ReplicaManager constructor 对象成员变量:count 之后=="+this.count);
}
static {
System.out.println("==ReplicaManager static 类成员变量:flushInterval=="+ReplicaManager.flushInterval);
System.out.println("==ReplicaManager static 成员变量:replicas=="+ReplicaManager.replicas);
System.out.println("==ReplicaManager static 静态代码块执行==");
loadReplicaFromDisk();
}
public static void loadReplicaFromDisk(){
System.out.println("==ReplicaManager loadReplicaFromDisk =从本地加载副本");
ReplicaManager.replicas = new HashMap<String, Replica>();
}
public void load(){
System.out.println("==ReplicaManager load =ReplicaManager加载");
}
public static void main(String[] args) {
ReplicaManager manager = new ReplicaManager();
manager.load();
}
}
AbstractReplicaManager
:
public class AbstractReplicaManager {
private int size;
static {
System.out.println("==AbstractReplicaManager static== ");
}
public AbstractReplicaManager(int size){
System.out.println("==AbstractReplicaManager constructor before== size"+this.size);
System.out.println("==AbstractReplicaManager constructor excuting== ");
this.size = size;
System.out.println("==AbstractReplicaManager constructor after==AbstractReplicaManager size"+this.size);
}
}
运行时候输出:
总结
:在使用类的时候我们创建对象,创建对象的时候会先进行类相关信息的创建(静态类变量,静态代码块),然后执行对象信息的创建(构造器进行对象实现初始化)。在初始化阶段我们发现其对应的累属性跟成员属性已经有默认值了,所以在准备阶段就已经赋值了,在初始化类的时候会想执行其父类的初始化。
类加载器有哪些?
JVM的类加载起具有亲子层级管理,主要分为4级:
启动类加载器:Bootstrap ClassLoader:主要负责加载我们安装在机器上的Java目录下核心类。
扩展类加载器:Extension ClassLoader:负责加载安装到机器上Java目录下的lib/ext。
应用类加载器:Application ClassLoader负责加载ClassPath环境变量下类,可以理解为加载你写好的java代码。
自定义类加载器:自定义类加载器,根据自己需求加载你的类。
类加载器的规则
为了避免重复加载一个类:JVM类加载机制使用双亲委派机制,比如你的自己写大代码执行时候,会先询问让应用类加载器去加载,然后应用类加载器去询问扩展类加载器是否可以加载,扩展类加载器去询问启动类加载器去加载。然后启动类加载器去在对应职权范围内发现没有找到这个类,那么就让其子扩展类加载器去加载,然后扩展类加载器去对应职权范围内没有发现的话交给器子类去加载,然后应用类加载器去加载
2.2 JVM的内存区域
我们通过字节码执行引擎执行代码然后将用到的类通过类加载器加载到内存之后,就会使用这些类执行我们的代码。首先我们会把类加载到存放类信息以及常量的方法区(由于类只会加载一次,所以此方法去是线程共享的),执行的代码后我们需要记录下字节码执行引擎执行的位置,所以需要程序计数器,记录下每一个线程对方法执行到的位置,加载完类之后会创建对象将对象存放到java堆内存中(线程共享),对象创建之后会执行对象的方法,由于方法的执行是多线程的,所以此时会为此方法创建栈帧,然后将方法跟对象的局部变量压栈道Java虚拟机栈,同样如果方法底层调用native方法时候还会将其native方法压入本地方法栈中。当然还有不其他内存,叫做堆外内存:NIO中allocateDirect创建的内存空间不属于JVM,而是在堆外分配的内存空间。
2.3 什么是垃圾,什么情况下进行回收?
JVM的垃圾回收机制是用来干嘛的?为什么要垃圾回收?
对象的分配与引用
字节码执行引擎执行机器指令的时候,如果执行到某一个方法时候,会分配一个线程对应的Java虚拟机栈,并创建一个方法栈帧;然后将方法跟创建的局部变量压入栈帧。局部变量如果是创建的一个对象,那么会在java堆内存分配,并让java虚拟机栈的局部变量引用java堆内存对象。
一个方法执行完毕之后会怎么样?
方法执行完毕之后,其方法会从对象的线程的java虚拟机栈中出栈,然后方法的栈帧消失,局部变量小时,此时在Java堆内存里面的对象将可能变成未被引用的对象,变成垃圾对象。
不再需要的那些对象应该怎么处理?
JVM一旦启动,他就会自带一个垃圾回收的后台线程,这个线程会在后台在触发GC时候会不断检查JVM堆内存里面的各个实例对象。java堆内存里面的对象没有被局部变量引用之后,他还占用着空间,所以我们需要使用JVM的垃圾回收机制去回收这个对象。
什么是JVM中的"垃圾"?
ava堆内存里面的实例对象,没有任何一个方法的局部变量指向他,也没有任何一个类的静态变量,包括常量指向他。
什么是JVM中的垃圾回收?
JVM的后台垃圾回收线程定期回收垃圾对象,从内存里面清除掉,让他不再占用内存。
3、JVM的工作原理。
如下代码执行:
public class HDFS {
private static ReplicaFetcher fetcher = new ReplicaFetcher();
public static void main(String[] args) {
loadReplicasFromDisk();
fetcher.loadReplicaFromRemote();
}
public static void loadReplicasFromDisk(){
ReplicaManager manager = new ReplicaManager();
manager.load();
}
}
我们结合上面的代码从加载到执行最后消失的JVM工作原理图.
。
上图说明了:
1、执行多个方法的调用时,如何把方法的栈帧压入线程的Java虚拟机栈?
2、栈帧里如何放局部变量?
3、如何在Java堆里创建实例对象?
4、如何让局部变量引用那个实例对象?
5、方法运行完之后如何出栈?
6、垃圾回收是如何运行的?
扩展
加载到方法区的类会被回收吗?什么时候被回收呢?为什么?
在此情况下,方法去里面的类会被回收:
1、首先,该类的所有实例变量都已经从Java堆内存里被回收。
2、其次加载这个类的ClassLoader已经被回收。
3、最后,对该类的Class对象没有任何引用。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。