JAVA类加载阶段详解
首先我们先来看一道面试题
1 | public class Demo03_5 { |
问最后输出什么?
从大的阶段来说,类加载一共分为3个阶段,分别为加载,链接,初始化 .
加载
在类被编译成class文件后,需要通过类加载器,加载带方法区中.内部采用的是C++的instanceKlass描述java类,它的重要属性有:
- _java_mirror 即java的镜像类,例如对String来说,就是String.class,作用是把Klass暴露给java使用
- _super 即父类
- _fields 即成员变量
- _methods 即方法
- _constants 即常量池
- _class_loader 即类加载器
- _vtable 虚方法表
- _itable 接口方法表
如果这个类的父类还没有被加载,先加载父类.
加载和链接是可能交替运行的.
链接
链接又分为三个小阶段,分别为验证,准备,解析.
验证
验证:验证类是否符合JVM规范,进行安全性检查.
这里我们使用Sublime等支持二进制的编辑器修改class的魔术,将cafe babe改成cafe baby,然后再控制台运行java类.
1 | java.lang.ClassFormatError: Incompatible magic value 1667327589 in class file Demo03_1 |
可以看出,抛出了一个ClassFormatError,这是由于类的格式错误所导致,这就是在验证阶段所做的事情.
准备
准备:为static变量分配空间,设置默认值
- static变量在JDK7之前存储于instanceKlass末尾,从JDK7开始,存储于_java_mirror末尾
- static变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值是在初始化阶段完成
- 如果static变量是final类型的基本类型或字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
- 如果static变量是fianl类型的,但属于引用类型,那么赋值在初始化阶段完成
通过下面的例子,我们来确定static变量分配空间和赋值是两个步骤
1 | public class Demo03_2 { |
然后使用javap反编译
1 | { |
首先,我们先了解下,在初始化话过程,会执行static{}方法,也就是
static int a;
通过字节码我们可以看出,只有对a的声明,并没有对a进行赋值.
因为我们只设定a为默认值,所以没有在
static int b = 10;
1 | 0: bipush 10 //准备个常量10 |
我们可以看出是在
static final int c = 20;
1 | static final int c; |
我们可以看出,没有进入
也就是没有执行初始化方法,所以final类型的基本类型是在准备阶段赋值.
static final String d = “30”;
1 | static final java.lang.String d; |
我们可以看出,final类型的字符串常量也是在准备阶段赋值
static final String e = new String(“40”);
1 | 5: new #3 // class java/lang/String |
我们可以看出final类型的引用类型,是在初始化阶段赋值.
解析
解析:将常量池中的符号引用解析为直接引用
这里我们可以通过Classloader.loadClass方法,不会触发解析和初始化的特性.来通过HSDB工具.
来查看,在未触发解析是,常量池中的引用对象,只是一个符号,并不知道具体的类的地址.只有在解析过后,才会把符号变为直接引用.
由于篇幅的原因,这里就不演示了.
初始化
初始化:初始化即调用类的
初始化发生的时机
发生初始化的情况:
- main方法所在的类,总是会被首先初始化
- 首次访问该类的静态变量或者静态方法时
- 子类初始化,如果父类还没有初始化,会引发父类的初始化
- 子类访问父类的静态变量,只触发父类的初始化
- Class.forName
- new 会导致初始化
不会导致类初始化的情况
- 访问类的static final静态常量(基本类型和字符串常量)不会触发初始化
- 类对象.class不会触发初始化
- 新建数组对象不会触发初始化
- 类加载器的loadClass方法
- Class.forName 的参数2 为false
案例:
1 |
|
现在,我们再回过头来看开始的那道面试题
很明显输出为:
1 | 10 |
最后,我们再学习一个类加载的应用
利用类加载机制写一个单例类
话不多说,直接上代码
1 | public class Singleton { |
以上的实现特点是:
- 懒惰实例化
- 初始化的时候线程安全是有保障的
以上仅为个人观点,如有错误,欢迎指正.谢谢!