Java类加载过程
首先,我们编写好的Java代码,经过编译变成.class
文件,然后类加载器把.class
字节码文件加载到JVM中,接着执行我们的代码,最后将类卸载出JVM。而从类加载到虚拟机到卸载出虚拟机的这一整个生命周期总共可以分为7个步骤,分别为加载、验证、准备、解析、初始化、使用和卸载,其中验证、准备和解析又称为连接阶段。接下来简单介绍下各个阶段是干嘛的。
加载是“类加载”的第一个阶段,就是将需要用到的类对应的.class字节码文件加载到虚拟机内存,并在方法区中生成一个java.lang.Class
对象,作为程序访问这个类的各种数据的访问入口。
public class Test {
public static void main(String[] args) {
User user = new User();
}
}
看一下上面这段代码,经过编译会生成两个字节码文件Test.class
和User.class
,接着会将包含main方法的这个类加载到虚拟机内存中开始执行,当执行到User user = new User()
,发现需要用到User类,就会将User.class
加载到内存中。所以简单的说,当需要用到哪个类时,就回去加载哪个类,Java的自带的核心类会在虚拟机启动时就会加载,包括包含main方法的启动类。但其实,类加载也挺复杂的,只是我了解的也不深,目前就理解成这样吧,后面再深入研究。
第二阶段验证,从字面上就可以看出这个阶段是来校验加载进来的.class
文件中的内容是否符合规范,毕竟编译成.class
文件后还是可以人为的对这个文件进行修改,那如果改的乱七八糟,压根不符合虚拟机的规范,那虚拟机就没法执行了,所以说这一步还是比较关键的。至于如何验证,还没有研究。
准备阶段我引用《深入理解Java虚拟机》中的一句话:准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这也比较好理解,看下面一段代码:
public class Test {
public static int value = 10;
}
当需要用到这个类时,会先将这个类加载到内存中,并验证字节码文件的合法性。验证通过后就会进行准备工作了,会为这个类中的类变量分配内存空间,就是上面的value变量,并给一个初始值。
注意,仅包括类变量,不包括实例变量和局部变量等,并且只是给一个初始值,int型的初始值是0,所以准备过后,value的值是0,而不是10,而真正赋值为10是在初始化阶段。我还在其它资料上看到,这一阶段也会给这个类分配内存空间,先给类分配内存,在给它里面的类变量分配内存。
解析阶段是将常量池中的符号引用替换为直接引用的过程,这一部分内容我还没搞懂,所以这里就不过多记录了,简单了解一下。
初始化阶段是类加载中核心的一步了,还是以上面的代码为例,准备阶段我们已经为value变量分配了内存空间并给了初始值,现在就是真正给value赋值的时候,把10赋给了value。如果类中还含有静态代码块,也会在这一阶段执行。这里还要一点要注意,初始化类的时候,如果父类还没加载和初始化,也会触发父类的加载和初始化。
使用就没什么好说了,初始化完就可以开始使用这个对象了。
卸载是类的生命周期中的最后一阶段,即将方法区中无用的类回收,而类需要同时满足下面3个条件才算无用的类:
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的
java.lang.Class
对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
同时满足上述3个条件的类即可回收,但不一定就会回收,可通过参数配置。
下面用一张图来简单展示类的加载流程:
以上就是Java类的加载过程,当然,只是简单的说明了一下,刚接触,还是有很多地方不清楚,先大概有一个这样的印象,后面再慢慢深入理解。
参考资料:
- 《深入理解Java虚拟机》
- 《从0开始带你成为JVM实战高手》