JVM类的生命周期详解
1. 前言
Java虚拟机为Java程序提供运行时环境,其中一项重要的任务就是管理类和对象的生命周期。它经过了哪些步骤呢?这篇文章就来简述一下关于Java类生命周期相关的知识。
2. 类的生命周期
当我们编写一个java的源文件后,经过编译会生成一个后缀名为class的文件,这种文件叫做字节码文件,只有这种字节码文件才能够在java虚拟机中运行,java类的生命周期就是指一个class文件从加载到卸载的全过程。如下图所示:
2.1 加载
- 类加载器:
把一个.class字节码文件加载到内存中(虚拟机内存)。jvm中所有的class都是被类加载器(ClassLoader)加载到内存中。默认采用双亲委派机制,其目的主要出于安全考虑。如下图所示:
- 双亲委派
当某个特定的类加载器它在接到需要加载类的请求时,这个类会首先查看自己已加载完的类中是否包含这个类,如果有就返回,没有的话就会把加载的任务交给父类加载器加载,以此递归,父类加载器如果可以完成类加载任务,就返回它,当父类加载器无法完成这个加载任务时,才会不得已自己去加载。这种机制就叫做双亲委派机制。
类加载过程:
当一个.class文件要被加载时。如果有自定义类加载器,会尝试先从自定义加载器中检查是否加载过,如果有那就无需加载了,如果没有,就去委托父加载器AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理会先检查自己是否已经加载过,如果没有再往上。注意这个过程,直到Bootstrap classLoader之前,都是没有哪个加载器自己选择加载的。如果父加载器无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。如下图所示:
2.2 连接
- 验证
验证.class文件是否符合JVM规范。这个阶段的目的就是保证加载的类是能够被jvm所运行。
- 准备
静态成员变量赋默认值。
- 解析
将类、方法、属性等符号引用解析为直接引用;常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
2.3 初始化
调用类初始化代码 ,给静态成员变量赋初始值。
- 通过new关键字实例化对象,访问final变量除外
- java.lang.reflect对类进行反射调用时
- 初始化子类的时候,父类首先初始化
- 作为程序入口直接运行时(也就是直接调用main方法)
2.4 卸载
在类使用完之后,如果满足下面的情况,类就会被卸载:
该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
加载该类的ClassLoader已经被回收。
该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了。
3. 码农来洞见
3.1 为什么类加载器要使用双亲委派机制?
java虚拟机只会在不同的类的类名相同且加载该类的加载器均相同的情况下才会判定这是一个类。如果没有双亲委派机制,同一个类可能就会被多个类加载器加载,如此类就可能会被识别为两个不同的类,相互赋值时问题就会出现。例如,如果有人想替换系统级别的类:String.java。篡改它的实现,但是在这种机制下这些系统的类已经被Bootstrap classLoader加载过了,所以并不会再去加载,从一定程度上防止了危险代码的植入。
3.2 为什么类加载的过程中顺序不是固定的?
类加载的过程中并不是所有情况下都是按阶段顺序进行的,其中加载、验证、准备、初始化、卸载是固定顺序开始的,解析阶段不一定。解析在某些情况下可以在初始化阶段之后再开始,这也是为了支持运行时绑定(也成为动态绑定)。还有就是每个阶段不一定会按部就班地“进行”或“完成”,是因为这些阶段通常是互相交叉地混合进行的,通常会在一个阶段执行的过程中调用激活另一个阶段。