Java类的加载机制

类不被初始化的情况

  1. 对于静态字段,只有直接定义这个字段的类才会变初始化,子类引用父类的静态字段不会导致子类的初始化,只会初始化父类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SuperClass {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;


}

public class SubClass extends SuperClass {
public static int aa=32;
static {
System.out.println("SubClass init!");
}
}
  1. 数组的定义不会导致类的初始化

    1
    SuperClass[] sca = new SuperClass[10];
  2. 使用静态常量不会导致类的初始化,因为常量在编译阶段就会存入调用类的常量池,本质上没有引用到定义类的常量,类在被编译成class之后,静态常量和类就没有关系了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    public class ConstClass {

    static {
    System.out.println("ConstClass init!");
    }

    public static final String HELLOWORLD = "hello world";

    }


    public class Test {

    @org.junit.Test
    public void test() {
    System.out.println(ConstClass.HELLOWORLD);
    }

    }

类加载过程7阶段

加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这个顺序按部就班的执行。

何时会执行加载的第一个过程?
这么五种情况是肯定会执行的

  1. 在遇到new 、读取或者设置一个类的静态字段(final除外,final在编译期间就放入了常量池)的时候,以及调用一个类的静态方法的时候。
  2. 使用反射调用的时候,如何类没有被初始化则会先触发其初始化。
  3. 类没有没有初始化的时候,如果其父类没有初始化。则需要先触发其父类的初始化。
  4. 执行一个main()方法
  5. 当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
    类的加载过程7个阶段

1. 加载

  • 加载是类加载的过程的一个阶段,加载到我们的JVM需要需要经过三个过程
  1. 通过一个类名来获取到此类的二进制字节流
  2. 将字节流的静态存储结构转化为方法区运行时的数据结构
  3. 在内存中生成这个类的 Class对象,作为方法区的访问入口。
    JVM加载类图示过程

几种类加载器

2. 验证

  • 验证这个步骤按照深入JAVA虚拟机一书描述的是,很重要的一个步骤。因为这个步骤需要保证,加载进来的Class文件是符合虚拟机规范的,并且不会对虚拟机造成安全危害,这个阶段只要有以下三个操作。
  1. 文件格式验证
    这个步骤主要是检查字节流是否符合Class文件格式规范,是否以魔数0xCAFEBABE开头,版本号,常量问题。

下面是一个class得二进制字节码,cafe开头的就是魔数,它是JAVA之父自己定义的一个规则,CafeBabe代表咖啡宝贝,这个也符号Java的咖啡标志。
魔数

  1. 元数据验证
    这个阶段只要是对字节码进行语义分析,以保证字节码符合Java语义的规范,可能包含的检查有这个class是否有父类,是否集成了不允许继承的类比如final修饰的类,是否实现了父类要求实现的接口,字段是否产生了矛盾。
  2. 字节码验证
    这阶段是验证中最复杂的一步,主要是确定class的语义是否合法、符合逻辑,对类的方法体进行验证分析,保证类的方法不会对虚拟机安全造成危害。

  3. 符号引用验证
    这个阶段主要是检查class的一些访问修饰符是否符合规范,符号引用验证的目的是确保解析动作能正常执行。

    3. 准备

    准备阶段是正式为类变量分配内存并设置类初始值的阶段,分配static修饰的变量,这些变量所使用的内存都将在方法区中分配。

1
public static int value = 123

value变量在被分配的时候,它的值不会是123而是默认的0。因为这个过程还未执行到Java代码,赋值为123的操作将在初始化阶段完成。

4. 解析

  • 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,
  1. 符号引用以一组符号来描述所引用的目标,它可以是新形势的字面量。
  2. 直接引用是指向目标的指针、相对偏移量或者一个能直接定位到目标的句柄。

解析类型主要有

  1. 类接口的解析
    1. 字段解析
  2. 类方法解析
  3. 接口方法解析

5. 初始化

类初始化是类加载过程中的最后一步,到了初始化阶段虚拟机才真正开始执行Java程序代码,准备阶段变量已经赋值过系统要求的初始值,而初始化阶段则回去初始化class的类变量和其他资源。

6. 使用

到了使用这一步就说明class已经正确的执行完了类加载的过程,class已经被加载到了虚拟机中,能被Java代码正确调用的过程。

7. 卸载

该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。

  • 加载该类的ClassLoader已经被GC。
  • 该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法