虚拟机类加载机制
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
类型的生命周期
加载,验证,准备,初始化,卸载这五个阶段的顺序是确定的,解析阶段则不一定,当然这些阶段顺序确定,不是说他们是一个个顺序进行,而是按顺序开始,这些阶段通常是互相交叉地混合进行的。
什么时候开始第一阶段“加载”?
1)遇到new ,getstatic,putstatic,invokestatic这四条字节码指令:
- 使用new关键字实例化对象的时候
- 读取或设置一个类型的静态字段的时候
- 调用一个类型的静态方法的时候
2)使用java.lang.reflect包的方法对类型进行反射调用的时候
3)当初始化类时,发现其父类还没有进行初始化
4)虚拟机启动时,用户需要指定一个要执行的主类(main),虚拟机会先初始化这个类
5)使用JDK1.7 的动态语言支持的时候,如果 java.lang.invoke.MethodHandle 实例最后的解析结果REF_getStatic 、REF_putStatic、REF_invokeStatic的方法句柄,句柄对应的类会被初始化
通过子类引用父类中定义的静态字段,只会触发父类的初始化。至于是否会触发子类的加载和验证,取决于虚拟机的具体实现(HotSpot不会加载)。
通过数组定义来引用类,如 A[] ints = new A[10] , 不会触发A 类的初始化。而是会会触发名为 LA的类初始化。它是一个由虚拟机自动生成的、直接继承于Object 的子类,创建动作由字节码指令 newarray 触发。这个类代表了一个元素类型为 A 的一位数组,数组中的属性和方法都实现在这个类中。Java 语言中数组的访问比C/C++ 安全是因为这个类封装了数组元素的访问方法。
常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
一个接口初始化时,不要求其父接口全部初始化,只有在真正使用到父接口(如引用接口定义的常量)才会初始化
类加载的过程
1.加载
加载阶段要完成三件事:
1)通过一个类的全限定名来获取定义此类的二进制字节流
2)将这个字节流所代表的静态存储结构转化成方法去的运行时数据结构
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
数组类本身不通过类加载器创建,它是由Java虚拟机直接在内存中动态构造出来的。
2.验证
目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身安全
1)文件格式验证
验证字节流是否符合Class文件格式的规范,保证输入的字节流能正确解析并存储于方法区之内。
2)元数据验证
对字节码描述的信息进行语义分析
3)字节码验证
通过数据流分析和控制流分析,确定程序语义是合法的,符合逻辑的。(方法体校验)
4)符号引用验证
发生在虚拟机将符号引用转化为直接引用的时候(解析阶段)。检查该类是否缺少或者被禁止访问它依赖的某些外部类,方法,字段等资源。
3.准备
为类中定义的变量(静态变量)分配内存并设置类变量初始值(通常情况下是零值)。
4.解析
将常量池中的符号引用替换成直接引用
符号引用:以一组符号来描述所引用的目标,不一定是以经加载到虚拟机内存当中的内容
直接引用:可以直接指向目标的指针,相对偏移量或句柄
5.初始化
执行类构造器()方法的过程。
()方法是由编译器自动收集类中的所有类变量的复制动作和静态语句块中的语句合并产生的。
先执行父类的()方法;且()方法必须加同步锁。
类加载器
实现类加载阶段的”通过一个类的全限定名来获取描述该类的二进制字节流“。对于任意一个类,必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性(比较两个类是否”相等“,只有在这两个类是由同一个类加载器加载的前提下才有意义)
双亲委派模型
三个类加载器:
-
启动类加载器(Bootstrap ClassLoader):主要负责加载 JDK 安装目录下的核心类库(比如/lib目录下的类),这些核心类库是JVM运行时自身需要用到的。
-
拓展类加载器(Extension ClassLoader):主要负责加载 JDK 安装目录下的扩展类库(比如/lib/ext目录下的类)。
-
应用程序类加载器(Application ClassLoader):负责加载用户自己开发的Java类。
除了启动类加载器没有父级,其他的类加载器都有父级。类加载器之间的父子关系一般不是继承,而是使用组合关系复用父类加载器的代码。
双亲委派模型:
如果一个类加载器收到
类加载请求
,他首先不会自己
去尝试加载
这个类,而是把这个请求委派
给父类
加载器去完成,每一个层次的类加载器都是如此,因此所有的类加载器请求都应该传送到最顶层的启动类加载器
,只有当父级
的类加载器反馈自己无法完成
这加载请求,子类
加载器才会尝试自己去完成加载