类加载过程
类的加载是由类加载子系统负责读取class文件执行,分为三个阶段:
第一阶段:加载阶段
第二阶段:链接阶段
第三阶段:初始化阶段
加载阶段(loading):
1.通过一个类的全限定名获取定义此类的二进制字节流;
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;(方法区根据jdk版本不同,1.7以前叫永久代,之后叫元空间)
3.在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口 。
链接阶段(linking):
1.验证(vertify)
验证class文件的合法性/准确性;
2.准备(prepare)
为类变量分配内存并设置初始值;
如果变量使用final修饰static的变量则是常量,常量在编译时已经分配了值,准备阶段会被显示的初始化;
实例变量不会分配初始化,类变量存放在方法区中,而实例变量会随着对象一起分配到java堆中;
3.解析(resolve)
将常量池内的符号引用转换为直接引用的过程;
初始化(initialization):
执行类构造器方法<clinit>()的过程,javac编译器会自动收集类的所有类变量的复制动作和静态代码块中的语句合并在一起而来;
构造器方法中的指令按语句在源文件出现的顺序执行;
<clinit>()不同于类的构造器,构造器是虚拟机视角下<init>()
若该类有父类,jvm会保证子类的<clinit>()执行之前父类的<clinit>()先执行完毕
虚拟机必须保证一个类的d<clinit>()方法在多线程下被同步加锁;
public class ClassInitTest { private static int num = 1; static{ num = 2; number = 20; System.out.println(num); //System.out.println(number);//报错:非法的前向引用。 } private static int number = 10; //linking之prepare: number = 0 --> initial: 20 --> 10 public static void main(String[] args) { System.out.println(ClassInitTest.num);//2 System.out.println(ClassInitTest.number);//10 } }