Java类加载机制
Java类加载:三个步骤(简介)
- 类的加载(Load)
- 类的连接(Link)
- 类的初始化(Initialize)
- 类的加载:将类的class文件读入内存,并为之创建一个java.lang.Class的对象,此过程由类加载器(ClassLoader)完成
- 类的连接:将类中的数据加载到各个内存区域中
- 类的初始化:JVM负责对类进行初始化
类加载:深入理解
类的完整生命周期:加载、连接(验证、准备、解析)、初始化、使用、卸载
一、加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.class对象。
-
通过一个类的全限定名来获取其定义的二进制字节流
即依据全限定名找到硬盘中对应的.class文件
全限定名:完整包名.类名;二进制字节流:JVM解释后产生的.class文件
-
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
从硬盘加载到内存的方法区中
-
在堆中生成一个代表这个类的Class对象,作为方法区中这些数据访问的入口
- 注:相对于类加载过程的其它阶段而言,加载阶段是可控性最强的阶段,因为程序员可以使用系统的类加载器加载,还可以使用自己的类加载器加载。
二、连接(验证、准备、解析): 将Java类的二进制代码合并到JVM的运行状态之中的过程。
1.验证: 确保加载的类信息符合JVM规范,没有安全方面的问题 。
1)文件的格式验证:验证.class文件字节流是否符合class文件的格式的规范,并且能够被当前版本的虚拟机处理。
比如:cafe babe对应的java,0000 0034对应的JDK1.8
2)元数据验证:主要是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求 。
比如说验证这个类是不是有父类,被继承的类是否可以被继承等等
3)字节码验证:这是整个验证过程中最复杂的阶段,主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
比如保证任何跳转指令都不会跳转到方法体以外的字节码指令上。
保证任何时刻操作数栈的数据类型和指令代码序列都能配合工作,例如不会出现类似于"栈放置了一个int类型的数据,使用的时候按照long类型来加载入本地变量表中"这样的情况。
保证方法体内的类型转换都是有效的,防止类型转换的乱用。
4)符号引用验证:验证的最后一个阶段,发正在虚拟机将符号引用转化为直接引用的时候。验证符号是否能够找到它对应的东西的地址。
比如通过字符串的全限定名是否能找到对应的类。这个符号引用的类是否可以被当前类访问。
-
注:对整个列加载机制而言,验证阶段是一个很重要但是非必要的阶段,如果我们能够确保没有问题,那么我们就没有必要去验证。在这方面,我们可以进行JVM调优。
-
使用-Xverfty:none可以关闭大部分验证
-
准备 - 重要
准备阶段主要为静态变量分配内存并设置初始值。这些内存都在方法区分配。
- 静态变量:这个阶段分配内存,但分配程序员定义的值,会分配初始值。
- 初始值:数据类型默认值,而不是代码中被显式赋予的值。
- 实例变量:这个阶段不分配内存,因为实例变量主要随着对象的实例化一起分配到堆内存中。
- 常量:在这个阶段被放入调用类的常量池
//静态变量 //在这个阶段value被赋上了int类型默认值0。在初始化阶段,value将被赋值为1 public static int value = 1; //常量 //final修饰后,value为常量,在这个阶段被存入调用类的常量池,因此,这个阶段它就已经赋值为1 public static final int value = 1;
-
解析:虚拟机将常量池中的符号引用转化为直接引用的过程
-
初始化:类加载机制的最后一步,在这个阶段,java程序代码才开始真正执行。这个阶段就是执行类构造器clinit()方法的过程。
- 静态变量:准备阶段已经赋过类型默认值,在这个阶段,静态变量将被赋上代码中定义的值。
- clinit()方法:链接(还没学)
三、使用:当JVM完成初始化阶段之后,JVM便开始从入口方法开始执行用户的程序代码
四、卸载:当用户程序代码执行完毕之后,JVM便开始销毁创建的Class对象,最后,负责运行的JVM也推出内存