作者:泽泽vlog 2020-05-20 22:13:26
云计算
虚拟化 验证是连接阶段的第一个步骤,验证的目的是为了确保`.class`文件中的字节流所包含的信息是符合当前虚拟机的要求,并且不会危害到虚拟机自身的安全的。
开门见山
首先引入一道面试题
为神马?为神马?这要从java的类加载时机说起。
本来是准备把分析结果写在最下面的但是怕大家没有耐心看到最后我这边先大概分析下,如果看不懂下面的分析。真心建议大家能看到最后,文章不算长。
类的加载时机
类从被加载到虚拟机内存中开始,直到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三个部分统称为连接(linking)。
其中加载、验证、准备、初始化和卸载五个步骤的顺序都是确定的,解析阶段在某些情况下有可能发生在初始化之后,这是为了支持 Java 语言的运行期绑定的特性。
何时开始类的初始化
接口的加载过程与类的加载过程稍有不同。接口中不能使用`static{}`块。当一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有真正在使用到父接口时(例如引用接口中定义的常量)才会初始化。
被动引用例子
示例一
对于静态字段,只有直接定义这个字段的类会被初始化,如果是通过子类引用父类的字段,父类会被初始化,子类不一定会被初始化,子类会不会被初始化 JVM 虚拟机规范并没有明确规定,取决于虚拟机的具体实现
上面代码运行之后输出结果如下所示
- SuperClass init!
- The value is 24
示例二
上面代码运行之后,并不会输出 “SubClass init!“,因为在上面Demo#main()方法中,并没有初始化SubClass类,而是初始化了一个SubClass[]数组类,SubClass[]数组类代表了一个元素类型为SubClass的一维数组,继承自Object类,由newarray字节码创建。
示例三
上面代码运行之后也并不会输出”Constant init!“,因为这涉及到一个概念 —- “常量传播优化”。虽然在代码中Demo类引用了Constant类中的常量VALUE,但是在编译阶段,会将VALUE的实际值”Hello World!“放到Demo类中的常量池中,Demo类每次使用”Hello World!“常量的时候都会从自己的常量池中去找。Demo类不会持有Constant类的符号引用,所以Constant类也并不会被初始化。
类的加载过程
加载
在加载阶段有三个步骤:
在这个阶段,有两点需要注意:
验证
验证是连接阶段的第一个步骤,验证的目的是为了确保`.class`文件中的字节流所包含的信息是符合当前虚拟机的要求,并且不会危害到虚拟机自身的安全的。
`Java`语言本身是相对安全的语言,使用Java编码是无法做到如访问数组边界以外的数据、将一个对象转型为它并未实现的类型等,如果这样做了,编译器将拒绝编译。但是,`Class`文件并不一定是由`Java`源码编译而来,可以使用任何途径,包括用十六进制编辑器(如`UltraEdit`)直接编写。如果直接编写了有害的“代码”(字节流),而虚拟机在加载该Class时不进行检查的话,就有可能危害到虚拟机或程序的安全。
验证阶段对于虚拟机的类加载机制来说,不一定是必要的阶段。如果所运行的全部代码确认是安全的,可以使用`-Xverify:none`参数来关闭大部分的类验证措施,以缩短虚拟机类加载时间。
准备
准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
有几点需要注意:
1.在方法区中分配内存的只有类变量(被`static`修饰的变量),而不包括实例变量,实例变量将会跟随着对象在 Java 堆中为其分配内存
2.初始化类变量的时候,是将类变量初始化为其类型对应的`0`值,比如有如下类变量,在准备阶段完成之后`val`的值是`0`而不是 `123`,为 `val`复制为`123`,是在后面要讲的初始化阶段之后
- //在准备阶段value初始值为0 。在初始化阶段才会变为123 。
- public static int val=123;
3. 对于常量,其对应的值会在编译阶段就存储在字段表的`ConstantValue`属性当中,所以在准备阶段结束之后,常量的值就是`ConstantValue`所指定的值了,比如如下,在准备阶段结束之后,`val`的值就是`123`了。
- public static final int val = 123;
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用(Symbolic Reference):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
直接引用(Direct Reference):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,如果有了直接引用,那么引用的目标必定已经在内存中存在。
初始化
类的初始化阶段才是真正开始执行类中定义的 Java 程序代码。初始化说白了就是调用类构造器` ()`的过程,在类的构造器中会为类变量初始化定义的值,会执行静态代码块中的内容。下面将介绍几点和开发者关系较为紧密的注意点
1. 类构造器` ()`是由编译器自动收集类中出现的类变量、静态代码块中的语句合并产生的,收集的顺序是在源文件中出现的顺序决定的,静态代码块可以访问出现在静态代码块之前的类变量,出现的静态代码块之后的类变量,只可以赋值,但是不能访问,比如如下代码
2. ` ()`类构造器和` ()`实例构造器不同,类构造器不需要显示的父类的类构造,在子类的类构造器调用之前,会自动的调用父类的类构造器。因此虚拟机中第一个被调用的` ()`方法是 `java.lang.Object`的类构造器
3. 由于父类的类构造器优先于子类的类构造器执行,所以父类中的`static{}`代码块也优先于子类的`static{}`执行
4. 类构造器` ()`对于类来说并不是必需的,如果一个类中没有类变量,也没有`static{}`,那这个类不会有类构造器` ()`
5. 接口中不能有`static{}`,但是接口中也可以有类变量,所以接口中也可以有类构造器 ` {}`,但是接口的类构造器和类的类构造器有所不同,接口在调用类构造器的时候,如果不需要,不用调用父接口的类构造器,除非用到了父接口中的类变量,接口的实现类在初始化的时候也不会调用接口的类构造器
6. 虚拟机会保证一个类的` ()`方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的类构造器` ()`,其他线程会被阻塞,直到活动线程执行完类构造器` ()`方法
当前题目:看了这篇你还不懂JVM中的类加载机制?
网页URL:http://www.mswzjz.cn/qtweb/news13/340213.html
攀枝花网站建设、攀枝花网站运维推广公司-贝锐智能,是专注品牌与效果的网络营销公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 贝锐智能