类的生命周期和类的加载
类的生命周期
一个类的完整生命周期如下:
-
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的
解析阶段在某些情况下,可以在初始化阶段之后再开始 -
虚拟机规范规定,以下5种场景需要立即对类进行初始化工作
-
- 遇到特定字节码指令的时候
字节码指令:new、getstatic、putstatic、invokestatic
-
使用new关键字实例化对象
-
set/get类的静态字段(排除final修饰的字段)
-
调用类的静态方法
-
- 对类进行反射操作的时候
-
- 初始化一个类的时候,必须先初始化其父类
-
- 虚拟机启动的时候,用户执行执行的主类
包含main方法的类
- 虚拟机启动的时候,用户执行执行的主类
-
- MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,句柄所对应的类要初始化 ⛹️♀️
-
类加载过程
系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。
加载
类加载过程的第一步,主要完成下面3件事情:
-
通过类的全限定名获取类的二进制字节流
二进制字节流不一定需要从class文件中获取- 从jar包中加载
- 从网络中获取
- 运行时生成,动态代理技术
- …
-
将字节流所代表的静态存储结构转化为方法区中的运行时数据结构
-
在内存中生成字节流的class对象
一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 loadClass()
方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。
加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了。
验证
-
【目的】为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全
-
文件格式验证
-
【目的】验证字节流是否符合Class文件格式的规范,并且能够被当前版本的虚拟机处理
-
通过这个阶段的验证后,字节流才会进入内存的方法区中进行存储
-
-
元数据验证
-
【目的】对字节码的描述信息进行语义分析
-
验证点,比如:
-
是否有父类
-
是否继承了不允许被继承的父类
-
非抽象类是否实现了父类或接口中要求实现的方法
-
…
-
-
-
字节码验证
- 【目的】通过数据流和控制流分析,确定程序语义是否合法、符合逻辑
-
符号引用验证
-
发生在解析阶段,虚拟机将符号引用转化为直接引用的时候
-
【目的】确保解析动作能正常执行
-
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
- 【目的】为类变量在方法区分配内存并设置初始值
不包括实例变量 -
类变量为常量值(final修饰),在这个阶段会为变量初始化为指定的常量
public static final int value = 123;
基本数据类型的零值:
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。
一句话:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。
知识点:
符号引用就是字符串,这个字符串包含足够的信息,以供实际使用时可以找到相应的位置。你比如说某个方法的符号引用,如:“java/io/PrintStream.println:(Ljava/lang/String;)V”。里面有类的信息,方法名,方法参数等信息。
例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。
直接引用,当第一次运行时,要根据字符串的内容,到该类的方法表中搜索这个方法。运行一次之后,符号引用会被替换为直接引用,下次就不用搜索了。
- 直接引用是某一个类的方法,那么直接引用就是偏移量,通过偏移量虚拟机可以直接在该类的方法表中内存区域中找到方法字节码的起始位置。
- 直接引用是一个类时,那么直接引用就是其内存地址。
初始化
- 初始化是类加载的最后一步,也是真正执行类中定义的 Java 程序代码(字节码),初始化阶段是执行类构造器
<clinit> ()
方法的过程。
卸载
卸载类即该类的Class对象被GC。
卸载类需要满足3个要求:
- 该类的所有的实例对象都已被GC,也就是说堆不存在该类的实例对象。
- 该类没有在其他任何地方被引用
- 该类的类加载器的实例已被GC
所以,在JVM生命周期类,由jvm自带的类加载器加载的类是不会被卸载的。但是由我们自定义的类加载器加载的类是可能被卸载的。
只要想通一点就好了,jdk自带的BootstrapClassLoader,PlatformClassLoader,AppClassLoader负责加载jdk提供的类,所以它们(类加载器的实例)肯定不会被回收。而我们自定义的类加载器的实例是可以被回收的,所以使用我们自定义加载器加载的类是可以被卸载掉的。