JVM类加载机制
虚拟机类加载机制
类从被加载到虚拟机内存开始到卸载出内存为止,整个生命周期包含以下7个阶段,其中验证,准备,解析3个阶段被统称为连接。7个阶段中加载,验证,准备,初始化和卸载这5个阶段的顺序是确定的,而解析和使用阶段则不一样:某些情况下可以在初始化之后再开始解析(支持Java语言的运行时绑定-动态绑定或晚期绑定)。
image.png加载
Java虚拟机规范中没有进行强制约束,具体实现可以由虚拟机来自由把握。
加载阶段需要完成3件事:
- 通过一个类的全限定名称来获取定义此类的二进制字节流。
- 通过这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
定义类的二进制字节流不一定要从Class文件中获取,它可以以任意形式获取,比如从Zip包中读取,从网络,数据库中获取,运行时计算生成或者有其他文件生成等等。
数组类本身不通过类加载器创建,它是由虚拟机直接创建的,数组类的元素类型(Element Type,数组去掉所有维度后的类型)最终要靠类加载器去创建。
方法区中的数据存储格式有虚拟机实现自行定义。
加载阶段与连接阶段的部分内容是交叉进行的。
验证
目的:确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 文件格式验证
验证字节流是否符合class文件格式的规范并且能被当前版本的虚拟机处理。
- 元数据验证
对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。
- 字节码验证
验证阶段最复杂的阶段,目的是通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的。
- 符号引用验证
在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段中发生。
符号引用可以看做是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
验证阶段是一个非常重要的,但不是一定不要的(因为对程序运行期没有影响)的阶段。
准备
准备阶段是正式为类变量(被static修饰的变量,不包括实例变量,实例变量随对象实例化时随着对象一起分配在Java堆中)分配内存并设置类变量初始值(数据类型的零值)的阶段,这些变量所使用的内存都将在方法区中进行分配。
基本数据类型的零值
数据类型 | 零值 |
---|---|
int | 0 |
long | 0L |
short | (short)0 |
char | '\u0000' |
byte | (byte)0 |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不相同,如果有了直接引用,那么引用的目标必定已经在内存中存在。虚拟机规范中没有规定解析阶段发生的具体时间,只要在执行16个用于操作符号引用的字节码指令之前先对他们所使用的符号引用进行解析。
解析动作主要针对类或接口,字段,类方法,接口方法,方法类型,方法句柄和调用点限定符7类符号引用进行。
初始化
初始化阶段是执行类构造器<clinit>()方法的过程,<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,顺序由语句在源文件中出现的顺序决定。
虚拟机规范中规定有且只有以下5中情况必须立即对类进行初始化:
- 遇到new getstatic putstatic invokestatic 这4条字节码指令时,如果类没有进行初始化则需要先触发初始化。
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化则需要先触发初始化。
- 当初始化一个类时,如果其父类没有进行过初始化,则需要先触发其父类进行初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类。
- 当使用JDK1.7的动态语言支持是,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic REF_putStatic REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
上述5种场景中的行为被称为对一个类进行主动引用,除此之外所有引用类的方式都不会触发其初始化,被称为被动引用。
类加载器
实现——通过一个类的全限定名来获取描述此类的二进制字节流的代码模块称为“类加载器”。
对任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每个类加载器都拥有一个独立的类名称空间。
启动类加载器(Bootstrap ClassLoader)
由C++语言实现,是虚拟机吱声的一部分。
其他类加载器
其他类加载器都是由Java语言实现,独立于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader
扩展类加载器(Extension ClassLoader)由sun.misc.LauncherApp-ClassLoader实现
image.png双亲委派模型:除了顶层的启动类加载器外其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承的关系来实现,而是都使用组合关系来复用父加载器的代码。
工作时某个类加载器收到了类加载的请求,他首先不会自己尝试加载这个类,而是把请求委派给父类区完成,每个层次的类加载器都是如此,只有当父加载器反馈自己无法完成这个加载请求时(他的搜索范围中没有找到所需的类),子加载器才会尝试自己去加载。
破坏双亲委派模型:
- findClass()方法
- 线程上下文类加载器
- 用户对程序动态性的追求:代码热替换,模块热部署。