JVM类加载机制—结合代码分析

《深入理解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语句块要优于子类的变量赋值

到此,我们就可以明白一开头的输出情况是怎么出来的


到此,类加载过程就完成,接下来就是使用了。

猜你喜欢

转载自blog.csdn.net/jiangziya1531/article/details/82804361