本文共 4724 字,大约阅读时间需要 15 分钟。
虚拟机类加载机制:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的
Java类型。类的加载、连接、初始化都是在程序运行期间完成。
类的生命周期:加载(loading) --> 验证(verification) --> 准备(preparation) --> 解析(resolution) --> 初始化(initialization)
--> 使用(using) --> 卸载(unloading)。其中验证、准备、解析三个部分统称连接(linking)。有且只有这五种场景对主动触发初始化,这种行为成为主动引用。
被动引用:
public class SuperClass { static { System.out.println("SuperClass init!"); } public static int value = 123; } public class SubClass extends SuperClass { static { System.out.println("SubClass init!"); } } public static void main(String[] args) { System.out.println(SubClass.value); } 输出结果: SuperClass init! 123
通过数组定义来引用类,不会触发此类的初始化。
public static void main(String[] args) {
SuperClass[] su = new SuperClass[2];
}
什么都没有输出
常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量类的初始化。
public class ConstClass {
static { System.out.println("ConstClass init!");}public static final String HELLO_WORLD = "hello world";
}
public static void main(String[] args) {
System.out.println(ConstClass.HELLO_WORLD);
}
输出结果:
hello world类加载过程包括:加载、验证、准备、解析、初始化
虚拟机主要要完成:
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区中。
类加载:自定义类的加载器;系统提供引导类加载器
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全
验证字节流是否符合Class文件格式规范,并且能够被当前版本虚拟机处理
主要目的:包装输入的字节流能正确的解析并存储在方法区内,格式上符合描述一个Java类型信息的要求。
只有通过验证,字节流才会进入内存的方法区中进行存储,后续的验证阶段全部是几区方法区的存储结构进行的。对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范
通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
校验类的方法体,保证被校验类在运行时不会做出危害虚拟机安全的事件对类自身以外的信息进行匹配校验
目的:确保解析动作能够正常执行
发生在虚拟机将符号引用转换为直接引用时准备阶段是正式为类变量(被static修饰的变量)分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中分配。
例如,在准备阶段将value的值赋为0,在初始化的时候,设为123;public static int value = 123;
基本数据类型的零值
数据类型 | 零值 | 数据类型 | 零值 |
---|---|---|---|
int | 0 | boolean | false |
long | 0L | float | 0.0f |
short | (short)0 | double | 0.0d |
char | 'u0000' | reference | null |
byte | (byte)0 |
特殊情况:字段属性为ConstantValue属性,则会在准备阶段赋值,如
public static final int value = 123 ;
解析阶段:虚拟机将常量池内的符号引用替换为直接引用的过程
类初始化阶段是类加载过程的最后一步,也是真正开始执行类中定义java程序代码(或者说字节码)。
在准备阶段,变量已经赋过一场系统要求的初始值,初始化阶段根据程序员通过程序设定的值;或者说:初始化阶段是执行类构造器<clinit>()方法的过程。定义在之后的变量,在前面的静态语句块可以赋值,但不能访问。
接口的实现类在初始化是不执行接口的<clinit>()方法。
通过一个类的全限定名来获取描述此类的二进制字节流,这个动作在Java虚拟机外部实现,让应用程序自己决定如何获取所需的类,实现
这个动作的代码模块称为类加载器。对于任意一个类,都需要由加载他的类加载器和这个类本身一同确立在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。
也就是说:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类源于同一个Class文件,被同一个虚拟机加载,只要加载它们打的类加载器不同,这两个类就比定不相等。对于虚拟机来说,存在两种类加载器:
启动类加载器:将放在<JAVA_HOME>lib目录中的,或者被-XbootClassPath参数所指定的路径中的,并且是虚拟机识别的类加载
到虚拟机内存中。无法被java程序直接引用扩展类加载器(Extension ClassLoader):由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>libext目录的,
或者被java.ext.dirs系统变量所指定的路径中的所有类,开发者可直接使用。应用程序类加载器(Application ClassLoader):由sun.misc.Launcher$AppCLassLoader实现。这个类加载器是ClassLoader中
getSystemClassLoader()方法的返回值,故也称为系统加载器。负责加载用户类路径上所指定的类库,开发者可直接使用,如果没有自定义自己的类加载器,这个就是默认的类加载器。双亲委派模型:要求除顶层的启动类加载器外,其他加载器都应当有自己的父加载器。
工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把请求委派给父类加载器去完成,每一个层次的
类加载器都是如此,因此所有的加载请求最终都应该传到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子类加载器才会尝试自己加载。转载地址:http://kgpia.baihongyu.com/