类什么时候加载?
当虚拟机启动时,会先加载main方法所在的主类
除此之外,以下几种情况将触发类加载:
- 调用类的静态方法
- new对象
- 使用反射实例化对象
简单来说,就是需要用到类时就加载类
加载一个类时,如果其父类尚未加载,则会先加载父类
类加载过程
类加载主要有五个过程:
加载
加载其实就是将class文件转换为类对象的过程,共分为三个步骤:
- 根据类的全限定名加载对应的class文件
- 将其转换为方法区的运行时数据结构(JDK1.7之前),JDK1.7之后是放在堆中
- 在内存中生成一个java.lang.Class对象
值得一提的两点:
- class文件的来源并不一定是本地的class文件,只要是符合class文件结构的字节流都可以,比如来源于网络的二进制流,来源于数据库的二进制流,War包,运行时生成等
- 数组不由类加载器加载,而是JVM直接创建。
验证
验证就是校验加载的类是否符合虚拟机规范,是否有安全问题。
该阶段非常重要,决定了JVM是否能够承受恶意代码的攻击。
如果你足够信任所运行的代码,可以使用-Xverify:none
参数关闭,关闭后可以一定程度上加快类加载过程
验证的主要过程分为以下几步:
步骤 | 验证内容 |
---|---|
文件格式验证 | 验证class文件是否符合规范,例如: 1. 魔数校验:文件开头是否是CAFA BABE 2. 版本校验:当前JVM是否支持该版本的class 3. 常量池中的常量是否有不被支持的常量 4. 等等 |
元数据验证 | class描述的信息进行校验,确保符合虚拟机规范,例如: 1. 是否对final的类进行了继承 2. 是否有父类 3. 抽象方法是否都已实现 4. 等等 |
字节码验证 | 验证程序语法语义是否合法,即操作指令是否合规合法 |
符号引用验证 | 该动作发生在将符号引用转换为直接引用时,对常量池中各种信息进行校验,如: 1. 符号引用中引用的类是否存在 2. 符号引用中的类、字段、方法是否可以被当前类访问 3. 等等 |
准备
在准备阶段,JVM将为static修饰的变量分配内存,并初始化0值。
比如有一个静态变量为:
public static int value = 10;
在准备阶段,value的值是0,而不是10
注意:JDK1.7之前,此处分配内存在方法区,1.7之后在堆
解析
解析就是将符号引用替换为直接引用,这里不得不重点提一下二者之间的区别:
符号引用 | 直接引用 |
---|---|
class文件中,用一组符号来描述一个目标。 例如常量池中 CONSTANT_Class_info ,描述了一个类的全限定名(如java/lang/Object ) |
指向内存某个位置的指针 |
那么解析到底是怎么一回事呢?
比如现在有两个类,A和B,其中A中引用了B,在A的常量池中有B的符号引用,如下图:
当JVM的类加载器将A和B加载到内存的方法区(JDK1.7)中时,它们俩都各自有一个内存地址
A中B的符号引用替换为B的内存地址,以便将来使用B时,能够正确找到B
初始化
当一个类中有初始化变量需要赋值或静态语句块时,JVM会为其自动生成一个<clinit>
初始化方法。
初始化就是执行该方法,这是一个线程安全的方法,父类比子类先执行
接口没有静态语句块,但由于会有变量赋值,因此也会生成<clinit>
方法
<clinit>
方法就是执行赋值操作以及静态语句块