JVM类加载过程

类的生命周期

一个类的完整生命周期如下:

加载->链接->初始化->使用->卸载

 

 类的加载过程;

1、通过全类名获取定义此类的二进制字节流

2、将字节流所代表的静态存储结构转换为方法区的运行时数据结构。

3、在内存(hotspot中比较特殊 Class对象存放在方法区中)中生成一个代表该类的Class对象,作为方法区访问数据的访问入口。

(加载和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了)

连接过程:

1、验证

1.1文件格式验证:验证字节流是否符合Class文件格式的规范,例如是否以0xCAFEBABE开头、主次版本是否存在当前虚拟机的范围之内

1.2元数据验证:对字节码描述信息进行语义分析,以保证其描述信息符合java语言规范的要求,例如:这个类是否有父类,除java.lang.Object之外所有类都有父类、这个类是否被继承了不允许继承(final修饰的类)的类等。

1.3字节码验证:是一个复杂的阶段,通过数据流和控制流分析,确定程序语义是合法、符合逻辑的。比如保证任意时刻操作数栈和指令代码序列都能配合工作。

1.4符号引用验证:确保解析动作能正确执行

2、准备(为类变量分配内存并设置类变量初始值的阶段,这些内存都在方法区中分配。)

2.1 这个时候进行内存分配的仅包括类变量(Class Variables,即静态变量,被static关键字修饰的变量,只与类相关,因此被称为类变量),而不包括实例变量。实例变量会在对象实例化时随着对象分配在java堆中。

2.2 类变量所使用的内存都应该在方法区中分配,不过有一点需要注意的是:JDK7之前方法区和JDK8后是元空间

2.3 这里所设置的初始值往往是0L,false,0等,在这个准备阶段不会给代码中定义的比如public static int i = 11;只会赋值默认值0给它。

 

解析:

解析阶段是虚拟机将常量池内的符号引用替换为直接引用。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类。

符号引用:是一组符号来描述目标,可以是任何字面量。

直接引用:直接执行目标的指针、相对偏移量或一个间接定位到目标的句柄。

Example:在程序执行方法时,系统需要明确这个方法所在的位置。java虚拟机为每个类准备了一张方法表存放类中方法。

当需要调用一个类的方法的时候,只要知道这个方法在方法表中的偏移量就可以直接调用该方法。

总结:解析就是虚拟机将常量池中符号引用替换为直接引用,也就是得到类或者字段、方法在内存中的指针或者偏移量。

初始化

初始化阶段是执行方法<clinit>()的过程,是类加载的最后一步,这一步jvm才开始真正执行类中定义的java程序代码(字节码)

(<clinit>()方法是编译之后自动生成的)

对于<clint>()方法的调用,虚拟机会自己确保其在多线程环境的安全性,因为<clinit>()方法是带锁线程安全,所以在多线程环境下初始化可能会引起多个进程阻塞。

1、当遇到new、getstatic、putstatic或invokestatic这四条直接指令时,比如new一个类,读取一个静态字段、或调用一个类的静态方法时。

1.1当jvm执行new指令会初始化类。当程序创建一个类的实例对象。

1.2当jvm执行getstatic会初始化类。即当程序访问类的静态变量。

1.3当jvm执行putstatic指令会初始化类。即给程序给类的静态变量复制。

当jvminvokestatic指令时会初始化类,即程序调用类的静态方法。

2.java.lang.reflect包的方法对类进行反射调用Class.forname,newInstance。如果类没初始化,需要初始化

3.初始化一个类,如果其父类还没初始化,则初始化起父类。

 

 <clinit>方法是在类加载过程中执行的,而<init>是在对象实例化执行的,所以<clinit>一定比<init>先执行

 

卸载

1、卸载类的时候满足3个要求

1.1该类的所有实例对象都被GC,也就是说堆不存在该类的实例对象

1.2该类没有其他任何地方引用。

1.3该类的类加载器实例已经被GC

所以jvm声明周期,由jvm类加载器加载的类不会自动卸载。但是自定义的类加载器会被卸载。

jdk自带的bootstrapClassLoader,ExtClassLoader,AppClassLoader负责加载提供的类,他们肯定不会被回收。而我们自定义的类加载器的实例可以被回收。

posted @ 2022-05-20 15:43  雷雷提  阅读(121)  评论(0)    收藏  举报