《深入理解Java虚拟机》学习笔记
首先看下面的两个类
对于被主动引用的类,执行类加载操作,根据给定的全限定名获取这个类的二进制字节流(不一定是class文件,网络、动态代理等方式也会)并将二进制按虚拟机要求的格式存储在方法区(HotSpot存储用于访问方法区中这些类型的外部接口Class对象)。
当注释掉main方法中对Test类的实例化语句后,
可以看到,这里对父类、子类的static域进行了初始化,而构造函数没有涉及。对比得到:
当调用main()方法时初始化这个类(其实main也是一个静态方法),或当初始化子类时,会自动初始化未初始化的父类
当我们加入一个TestUnlce类,区别在于多了一个静态方法如下:
1.当在Test的main()方法中有下列三条语句的其中之一时
输出如下:
2.但是若语句如下,调用final修饰的static字段时:
执行结果如下:
当遇到new(使用new关键字实例化对象)、getstatic(读取一个类的非final修饰静态字段)、putstatic(设置一个类的静态字段)、invokestatic(调用一个类的静态方法)方法时,对相应的类执行初始化操作
当然,情况不只于集中,还存在反射、动态语音支持等,这里就事论事不做深入讨论
为了确保Class文件中的字节流中包含的信息符合当前虚拟机的要求,并不会危害自身安全。总的来说,就是在字节流中验证文件格式是否正确(就像是否存在魔数0xCAFEBABE、主次版本号是否能处理),对元数据进行语义分析。
最后一个阶段在于在JVM将符号引用化为直接引用时,验证类的各种修饰是否合法。
当我们对class文件的魔数或者版本号进行非法修改时:
大概效果如下,由于存在编码问题,主要的是问题的根源:
就像当我们使用Test**调用了一个不存在的方法**时:
对于上述情况,就会出现不同的Error,JVM会直接中断类加载过程而拒绝继续编译。
准备阶段即正式为类变量(static修饰的变量,不包括实例变量,奥斯卡电影排行榜实例变量会在对象实例化时一起分配在堆中)分配内存并设置初始值的阶段。对应到例子中的一直存在的两个类变量:
对于static修饰的y,此处被分配内存空间并赋值为int类型的初始默认值0;
对于static final修饰的y,它是一个有ConstantVlaue属性的字段,则会被直接完成赋值为10;
这些变量使用的内存都将在方法区中分配
解析阶段是JVM将常量池内的符号引用替换为直接引用的过程(此时与验证阶段存在交叉关系)
解析动作主要针对类、接口、字段、类方法、接口方法、方法类型、方法句柄与调用点限定符 7种符号引用。若出现不符合JAVA规范的情况,会抛出Error,就像验证阶段。
终于到了类加载的最后一步初始化!,在准备阶段变量已经被赋予过初始值,初始化阶段就到了按程序猿要求执行赋值的时候了。实际上:
初始化阶段是执行类的构造器的过程:
编译器收集的顺序是由语句在源文件中出现的顺序来决定的,静态语句块只能访问到定义在静态语句块之前的变量,对于之后的,只能赋值,不能引用
修改上述Test类至如下代码:
下面再说一个关于的规则:重新构造代码如下
JVM会保证子类的方法执行之前,父类的已经执行完毕,所以第一个完成的是java.lang.Object,由于父类先执行了,所以父类的static语句块要优于子类的变量赋值
到此,我们就可以明白一开头的输出情况是怎么出来的
到此,类加载过程就完成,接下来就是使用了。