类加载全过程这个基础章节的博客也是很早之前就想做个总结的了,直奔主题
(1)加载
加载JAVA编译阶段后产生.class文件,里面有各种表,各种属性和方法描述。所以第一步进行的加载阶段,就是通过一个C++的instanceKlass加载器读取.class字节码文件,并且在元空间(方法区)生成一个.class类对象,它加载时会产生一个_JAVA_MIRROR镜像对象放在堆中。
(2)验证
校验类的字节码文件是否符合JVM虚拟机规范,比如检查头部的Cafe Babe 魔数。
(3)准备
准备阶段就是给static 静态变量分配内存空间,设置默认值,在初始化阶段才附值。
如果是static final +基本类型(int,long…) ,准备阶段分配空间同时附值。因为基本类型静态常量在编译期已经确定值,省去了初始化附值多此一举操作。
如果是static final +基本引用类型(对象,数组),则分配空间,不附值。因为new创建对象和附值操作都必须是在加载完成初始化阶段后。
(4)解析
将常量池(.class里面那个constant pool )中的符号引用(一个方法,类,属性的描述)转化成为直接引用(方法,类,属性值在内存中的真是地址,一般长“@1024a129493b11001c”大概这个样子)。
(5)初始化
调用生成的构造方法(< init >()v)对该加载的类进行初始化,同时给引用类型附值,JVM底层保证了类初始化的线程安全。
借用大佬们总结的一张图:
彩蛋误区:
A:类加载全过程并不是一套组合拳流程跑完收工的,直接采用类加载器loadclass方法就不会发生解析和初始化。
B:在这里可以得知,基本类型的静态常量和字符串常量等等都不会导致一个类进入到初始化阶段,比如 static final int a = 1 ; static final string = “1111”;这种类的静态常量,可以直接通过类名.属性名获取,因为这种属性在准备阶段已经分配空间并且附值,不必初始化一个类也可以获取到。相反,如果调用实例方法,实例变量就必须是加载完初始化后才能使用,因为那时候它才存在。
C:Integer这种包装类型,就算是static final 的,也必须要等到初始化完成。因为我们直接定义的 static final Integer a = 1; 这种,其实是JVM默认给程序员的一种语法糖写法,它在.class字节码文件中调用的还是 int.valueOf() 方法进行装箱。
D:注意“初始化”和“实例化”的区别,实例化是你 new 来创建一个类对象。初始化则是类的加载一个环节,千万注意这两者区别很大,相对于静态方法来说,你可以不实例化一个对象而去调用里面的静态方法,可是绝对不可能不初始化那个类。
看例子:
还是那个结论,分清楚初始化和实例化,实例化是new,classForName , 之类的操作,初始化是类的加载过程,二者毫无关联,调用一个类的静态方法可以不实例化,但是不可能不加载初始化!
鞠躬!
来源:CSDN
作者:凉拌海蜇丝
链接:https://blog.csdn.net/whiteBearClimb/article/details/104136814