今天有在极客时间上看了一个jvm的课程。对jvm如何加载类的,理解更深了一步。这是基于看完《深入理解java虚拟机》一书,结合课程理解。
知识储备:jvm中,调用方法的指令分为五类
1. invokestatic 调用静态方法
2. invokevirtual 调用虚方法:简单理解是类的非static,private,super#func的实例方法
3. invokeinterface 调用接口方法
4. invokespecial 调用一些特殊的实例方法,例如初始化,私有,父类方法
5. invokedynamic 这个晚点再说,还没特别理解
虚拟机调用invokestatic和invokespecial指令是采用静态绑定,
而调用非final的invokevirtual和invokeinterface指令是采用动态绑定(多态实现原理)
加载
这一步骤,主要是通过ClassLoad加载字节流,根据字节流创建类过程。
验证
验证字节流是否符合各个标准
准备
这个阶段要做的事情:
- 分配内存空间
- 构造与类层次相关的数据结构:主要指的是虚方法(指的就是invokevirtual指令需要调用的方法)的动态绑定的方法表 第二条是新增的内容,这一块和方法的调用关系较大。
解析
这个阶段,会把各种符号引用转换为实际引用,切实的可以找到类,方法等所在的位置 这阶段,加载方法会因为指令的不同而有不一样的结果。
- invokestatic和invokespecial还有invokeinterface和invokevirtual的final方法,会解析成一个直接引用(目前我认为是直接引用是指向元数据区的数据的指针)
- invokeinterface和invokevirtual的非final方法,会解析成一个指向方法表索引的引用。(方法表索引在准备阶段已经生成)
初始化
类的初始化是线程安全的,所以可以保证单例延迟初始化中,保证一个类的实例
public class Singleton {
private Singleton() {}
private static class LazyHolder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
//遇到访问类的静态字段指令,会触发类的初始化。
//此时LazyHolder进行初始化,初始化又是线程安全的
//所以不会出现有多个Singleton实例被创建
return LazyHolder.INSTANCE;
}
}
重要知识点:类的初始化何时触发
-
虚拟机启动时,会初始化用户指定的主类
-
当遇到新建类实例的new指令的时候
-
遇到调用静态方法指令(invokestatic)
-
遇到访问静态字段的指令 (是getstatic吗?)
-
子类的初始化会触发父类初始化(因为会先初始化父类?继承关系初始化过程有疑问)
-
如果接口定义了defualt方法,有实现过这个接口的类初始化,会触发接口初始化
-
使用反射api对类进行调用时,初始化类
-
初次调用MethodHandle实例时,初始化MethodHandle指向的方法所在的类 这步,是对类进行一些初始化操作。这部分初始化分为两块:
-
java虚拟机直接完成:由final static一起修饰的基本类型和字符串(jvm会通过一定手段,把静态常量会放入运行时常量池(jdk9或者jdk8以后,字符串常量已经是放入堆内存中了))
-
java编译器将其放入一个叫clinit方法中初始化
列子 :只有第二次LazyHoder.INSTACE调用的时候,会触发初始化,可以自行尝试下面的代码public class Singleton { private Singleton() {} private static class LazyHolder { static final Singleton INSTANCE = new Singleton(); static { System.out.println("LazyHolder."); } } public static Object getInstance(boolean flag) { if (flag) return new LazyHolder[2]; return LazyHolder.INSTANCE; } public static void main(String[] args) { getInstance(true); System.out.println("----"); getInstance(false); } }