好久不写,但是这章真的重要
虚拟机规范中对于什么时候加载类并没有一个统一标准,但是初始化是有且只有六种情况才会进行,详见书中263页,下面记录几个问题和重点
------------------------------------------------------------
读取或设置一个类型的静态字段时会初始化该类,但不包括被final修饰的在编译器把结果放入常量池的静态字段:这里暗含了一个知识点,只有基础类型和String会在编译器被标注位ConstantValue放入常量池,也就是说其他类型的静态字段会触发初始化下面是实验部分
这个是代码部分,不管是在main()中调用InterfaceTest.i 还是InterfaceTest.s都不会触发InterfaceTest类初始化,也就会有打印InterfaceTest.o构建时的内容,但是如果调用InterfaceTest.o2就会触发初始化
//-XX:+TraceClassLoading
public class InitTest {
public static void main(String[] args) {
System.out.println(InterfaceTest.i);
}
}
interface InterfaceTest {
String s = "sa";
int i = 0;
int j = 999;
Object o2 = new Object();
Object o = new Object() {
{System.out.println("InterfaceTest!!!!!!!!!");}
};
}
使用javap -verbose 查看编译出的字节码(下图)发现,5-8行表示的是变量s的信息,其中可以看到在编译阶段就把“sa”存入了常量池并使s指向了“sa”。但是这并不是不需要触发类初始化的全部原因,因为外部类如果调用变量s还是需要加载整个类才能实现,编译器的操作其实在调用s的类里,也就是InitTest类中
我们同样查看InitTest的字节码,发现InterfaceTest.s的信息储存在3-4行,其中有Interface类的类名和一个字符串“sa”,也就是s的副本,并没有任何变量名称等其他信息后面也可以看到这个类名好像也没用上,可能是编译器的冗余优化
然后看一下main()方法的字节码,发现ldc的参数是#4,也就是InitTest常量池中保存的“sa”,完全没有用大#3上的InterfaceTest,这里可以看出编译后的InitTest和InterfaceTest间已经没有关系了,所以不会触发InterfaceTest的初始化
同时如果使用-XX:+TraceClassLoading打印加载的类就发现这种情况下InterfaceTest也没有加载,不过这个可能和虚拟机版本有关了
然而如果调用InterfaceTest.o就可以看到,会直接调用#3,然后顺着Fieldref找到类信息和变量名,这种情况下就必须加载InterfaceTest才能拿到相关对象
------------------------------------------------------------------
另一个问题,书中说当类在初始化时需要父类全部初始化完毕,而接口初始化时只需要初始化用到的父接口,下面是实验
//-XX:+TraceClassLoading
public class InitTest {
public static void main(String[] args) {
System.out.println(SubInterface.o);
}
}
interface InterfaceTest {
String s = "tttttest";
int i = 0;
int j = 999;
Object o2 = new Object();
Object o = new Object() {
{System.out.println("InterfaceTest!!!!!!!!!");}
};
}
interface InterfaceTest2 {
Object o = new Object() {
{System.out.println("InterfaceTest2!!!!!!!!!");}
};
}
interface SubInterface extends InterfaceTest, InterfaceTest2 {
int i = 0;
int j = InterfaceTest.j;
Object o = InterfaceTest.o;
}
运行后发现只初始化了InterfaceTest而没有初始化InterfaceTest2,两个类都被加载了
----------------------------------
非数组类的加载可由开发人员控制,可以自己定义字节流的获取方式,数组类型则由虚拟机自动生成字节码,所以通过构建数组来引用元素类不会导致元素类初始化
如果数组类的组件类型是引用类型,数组将被标识在加载该组件的类加载器的类名称空间上;如果是基础类型,会与启动类加载器关联
验证分为文件格式验证,元数据验证,字节码验证,符号引用验证
---------------------------------
类加载器与双亲委派模型
在虚拟机中类之间的相等关系是由类名和类加载器共同确认
双亲委派模型:当一个加载器收到一个加载请求后先交给父加载器,如果父加载器无法加载再由自己加载。这个模型的好处在于Java类随他的类加载器一起具备了一种层级关系,比如Object在各个加载器中都是同一个类
模块化系统:JDK 9引入模块化之后就用平台加载器代替了扩展类加载器,整个JAVA类库天然的满足了可扩展