5.JVM类的加载过程-加载,链接,初始化
1.类加载过程一:装载(Loading)
类加载分为加载、链接、初始化三步。
类的加载过程说明:
1.加载通过类的全限定名获取此类的二进制字节流。
2.将字节流所代表的静态存储结构转化层方法区的运行时数据结构。方法区在jdk7及以前称为永久代,JDK8及之后称为元空间。
3.在内存中生成一个大的Class对象。
加载class文件的方式有以下几种:
2.类加载过程二:链接(Linking)
链接分为验证、准备、解析三个小的阶段。
验证:
目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,保证虚拟机的安全。
验证过程主要包括:文件格式验证、元数据认证、字节码认证、符号引用认证。
如果验证不通过,会报一个验证出错的异常。
准备:
1.为类变量分配内存并且设置该类变量的默认初始值,即零值。例如int变量默认初始值为0,引用变量默认初始值为null。
例如在类Hello中有如下的一个变量定义:准备阶段会将a的默认初始值设置为0;在初始化阶段才会将a的值设置为1。
private static int a = 1;
- 1
2.对于final修饰的static变量。在编译时期就已经分配空间了,在准备阶段会显示初始化。
3.准备阶段不会为实例变量分配初始化,因为这时候还没有创建对象。类变量分配在方法区,实例变量分配在堆区。
解析:
1.常量池的符号引用转换为直接引用的过程。
3.类加载过程三:初始化(Initialization)
1.初始化阶段就是执行类构造器方法<clinit>()
的过程。
2.<clinit>()
不是自己定义的,是javac编译器自动收集类中的所有类变量(指的是静态成员变量)的赋值动作和静态代码块中的语句合并而来。(也就是将类变量的初始化以及静态代码块中的语句集合在<clinit>()
方法中)。
如果不存在static成员变量以及静态代码块,就不会生成<clinit>()
方法。例如下面的代码,就不会生成<clinit>()
。
3.<clinit>()
初始化的顺序是根据语句在源文件中出现的顺序决定的。例如下面的例子:number最终的值是10。在类加载的第二个阶段准备中,将number设置成了默认值0,然后再初始化阶段先设置成了20,再设置成了10。
在静态代码块中,可以给number赋值,但是不能去使用它。例如System.out.println(number);
会报非法的前向引用错误。
4.<clinit>()
不同于类的构造器,类的构造器是虚拟机视角下的<init>()
方法。(java类默认都存在默认的构造方法,所以在虚拟机视角下,对于任何一个java类都会生成一个<init>()
方法)。
5.若该类具有父类,JVM会保证子类的<clinit>()
方法执行前先执行父类的<clinit>()
方法。
例如下面的代码,Son继承了Fatcher类,所以会首先会加载Father类。加载Father类的过程,包含Loading,Linking,Initialization。在初始化阶段会生成并且执行Father类的<clinit>()
方法,给A设置成了2,;然后再加载Son类,执行Son类的<clinit>()
方法,将A的值设置成A。所以最终结果是2。
6.多线程情况下,一个类的<clinit>()
方法是被同步加锁的。(一个类只会被加载一次)
例如下面的代码:定义了一个DeadThread类,里面有一个静态代码块,所以在初始化阶段会生成这个类的<clinit>()
方法。
main函数中定义了两个线程,都会去调用DeadThread的static代码块, 所以两个线程都会去加载DeadThread类,要加载这个类,就会执行第三个初始化阶段的<clinit>()
方法。
但是System.out.println(Thread.currentThread().getName() + "初始化当前类");
只会被输出一次。这是因为<clinit>()
方法被加了锁。
public class DeadThreadTest { public static void main(String[] args) { Runnable r = () -> { //lambda 表达式 System.out.println(Thread.currentThread().getName() + "开始"); DeadThread dead = new DeadThread(); //调用DeadThread的static代码块 System.out.println(Thread.currentThread().getName() + "结束"); }; Thread t1 = new Thread(r,"线程1"); Thread t2 = new Thread(r,"线程2"); t1.start(); t2.start(); } } class DeadThread{ static{ if(true){ System.out.println(Thread.currentThread().getName() + "初始化当前类"); while(true){ } } } }