什么是类加载机制
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
类的加载过程
类的生命周期:
类的加载是图上的装载(load),链接(link),初始化(initialize)三个过程,其中链接又分为验证,准备,解析三个步骤。
装载
在装载阶段,虚拟机需要完成以下三件事情:
- 通过类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在java堆中生成一个代表这个类的java.lang.class对象,作为方法区中这些数据的访问入口
链接
验证
确保加载的类信息符合Jvm规范,无安全方面的问题,主要包括四种验证:文件格式的验证,元数据的验证,字节码的验证,符号引用的验证。
文件格式验证:验证字节流是否符合class文件的规范,如是否以0xCAFEBABE开头,主、次版本号是否在当前虚拟机的处理范围内,常量合理性验证等。
元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言要求的规范。如是否存在父类,父类的继承链是否正确等。
字节码验证:通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的。
符号引用验证:确保可以将符号引用转化为直接引用。
准备
为类变量分配内存并将其初始化为默认值,这些内存都将在方法区中分配。
类变量&实例变量
类变量指类中static修饰的数据成员,实例变量指非static修饰的数据成员。
注意:
- 这里不包含final修饰的static,因为final在编译的时候就已经分配了。
- 不会为实例变量分配,实例变量会在对象实例时随着对象一起分配在java堆中。
- 这里所设置的初始值是数据类型默认的零值(如0,null,false等),不是在java代码中被赋予的值。
如 public static int a=5;a在准备阶段后的值为0,在初始化阶段再把a赋值为5。
解析
将类的符号引用替换为直接引用。
符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用使无歧义定义到符号目标即可。符号常量与虚拟机的内存布局无关,引用的目标比一定加载到内存中。
直接引用:可以是指向目标的指针,偏移量或者能够直接定位的句柄。直接引用和虚拟机的布局相关,目标一定被加载到内存中。
初始化
对类的静态变量,静态代码块执行初始化操作。
在Java中对类变量进行初始值设定有两种方式。
1.声明时即指定初始值。
static int a=5;
2.使用静态代码块为静态变量指定初始值。
static int b;
static{
b=5;
}
类初始化的步骤:
1.假如这个类还没有被装载和链接,则会进行装载和链接。
2.假如该类的直接父类还没有被初始化,则先初始化其直接父类。
3.假如类中有初始化语句,则系统依次执行这些初始化语句。
类什么时候加载
Java虚拟机中没有强制约束什么时候开始类加载过程,这点可以交给虚拟机的具体时间来自由把握。但对与类的初始化,虚拟机规范则严格规定了几种情况必须对类进行初始化。
Java初始化有且只有5种场景(也称主动引用):
- 遇到new(用new实例对象),getstatic(读取一个静态字段),putstatic(设置一个静态字段),invokestatic(调用一个类的静态方法)这4条字节码指令时。
- 使用java.lang.reflect包的方法对类进行反射调用时。
- 当初始化一个类时,如果发现它父类还没有进行过初始化,则需要先初始化父类。
- jvm启动时,用户需要指定一个执行的主类(包含main方法的类)虚拟机会初始化这个类
- 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的结果为REF_getStatic\REF_putStatic\REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,需要进行初始化。
被动引用的情况(不会引起类的初始化):
- 通过子类引用父类的静态变量,不会引起子类的初始化,只会引起父类的初始化。
- 通过数组定义来引用类,不会触发此类的初始化。因为只是类的引用,还没有生成类的实例。
- 通过常量来引用类,不会触发类的初始化。常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
类加载器
类装载工作是由ClassLoader及其子类负责的,jvm预定义有三种类加载器。
启动类加载器(Bootstrap ClassLoader):根类加载器,负责将JAVA_HOME/lib下面的核心类库或_xbootclasspath选项指定的jar包等虚拟机识别的类库加载到内存中。
扩展类加载器(Extension ClassLoader):加载jre/lib/ext下的类库或者被java.ext.dirs系统变量指定的类。
应用程序类加载器(Application ClassLoader):加载classpath下的类库。
双亲委派机制
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的加载器都是如此,只有当父类加载器无法完成该类的加载请求时(该加载器的搜索范围中没有找到对应的类),子类加载器才会尝试自己去加载。
作用:
- 防止重复加载同一个类
- 确保核心类不被篡改