1、概述
JVM类加载机制:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的java类型的过程。
2、类加载过程
一般来说,我们把类加载过程主要分成三个步骤:加载、连接和初始化,其中连接又分为三个部分:验证、准备和解析,整个生命周期七个阶段如图所示:
(1)加载
JVM类加载阶段主要完成了三件事:
(2)连接
当类被加载之后,系统会为之生成一个对应的Class对象,接着将会进入链接阶段,链接阶段负责把类的二进制数据合并到JRE中,其具体分为下面三个阶段:
a、验证
是连接阶段的第一步,其目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全,验证主要又分为四个阶段:
b、准备
为类的静态变量在方法区分配内存,并赋默认初始值(0或null),例如static int a = 100,这个静态变量a就会在准备阶段赋默认值0。
需要注意的是,对于一般成员变量是在类实例化的时候,随着对象一起分配在堆内存当中,对于静态常量b(static final int b = 100)在准备阶段会直接赋值100,而对于静态变量,这个操作是在初始化阶段进行的。
c、解析
虚拟机将常量池内的符号引用替换为直接引用的过程。其中符号引用是指以一组符号来描述所引用的目标,它可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可,其与虚拟机实现的内存布局无关;而直接引用是指直接指向目标的指针、相对偏移量或是一个能够间接定位到目标的句柄,它与虚拟机实现的内存布局相关。
(3)初始化
类加载过程的最后一步,除了加载阶段,用户可以通过自定义类加载器参与,其它阶段都完全由虚拟机主导和控制,到了初始化阶段才真正开始执行java代码。类初始化的主要工作是为静态变量赋值程序设定的初始值,这与准备阶段的区别在于,准备阶段只是为静态变量赋值默认值,例如,static int a = 100,静态变量在准备阶段赋值默认值0,在初始化阶段赋值程序设定的值100。
在虚拟机规范中,规定了有且只有五种情况必须对类进行初始化:
(4)案例说明
public class Test1 {
public static int value1;
public static int value2 = 0;
public static Test1 test1 = new Test1();
public Test1(){
value1++;
value2++;
}
public static Test1 getTest1(){
return test1;
}
}
public class Test2 {
public static Test2 test2 = new Test2();
public static int value1;
public static int value2 = 0;
public Test2(){
value1++;
value2++;
}
public static Test2 getTest2(){
return test2;
}
}
测试结果:
Test1 test1 = Test1.getTest1();
Log.d(TAG,"Test1 value1:"+test1.value1);
Log.d(TAG,"Test1 value2:"+test1.value2);
Test2 test2 = Test2.getTest2();
Log.d(TAG,"Test2 value1:"+test2.value1);
Log.d(TAG,"Test2 value2:"+test2.value2);
Test1 value1: 1
Test1 value2: 1
Test2 value1: 1
Test2 value2: 0
我们可以看到两个类的打印结果不一样,在这里对两个类的加载过程做下说明:
Test1类的加载过程:
1.首先执行Test1 test1 = Test1.getTest1();
2.类的加载:加载Test1类
3.类的验证
4.类的准备:为静态变量分配内存,设置默认值,即设置value1、value2的默认值为0,test1的默认值为null
5.类的初始化:为静态变量设置程序中设定的值,首先执行public static int value2 = 0,所以value2的值为0,value1程序中没有设定值,所以还是默认值0;然后执行public static Test1 test1 = new Test1(),因为在Test1的构造函数中value1++,value2++,所以value1和value2的最终结果都为1
Test2类的加载过程:
1.首先执行Test2 test2 = Test2.getTest2();
2.类的加载:加载Test2类
3.类的验证
4.类的准备:为静态变量分配内存,设置默认值,即test2的默认值为null,value1、value2的默认值为0。
5.类的初始化:为静态变量设置程序中设定的值,首先执行public static Test2 test2 = new Test2(),因为Test2的构造函数中value1++、value2++,所以value1和value2的值为1,然后执行public static int value2 = 0,所以value2的值为0,因为程序中没有设定value1的值,所以value1和value2的最终结果是1和0
JVM提供了三种类加载器:
1、启动类加载器
最顶层的类加载器,由C++实现,是虚拟机自身的一部分,负责加载存放在JAVA_HOME\lib目录中,或被-Xbootclasspath参数所指定路径中的、且可被虚拟机识别的类库,不能被java程序直接引用。
2、扩展类加载器
由java实现,独立于虚拟机外部,能被java程序直接引用,负责加载JAVA_HOME\lib\ext目录中的、或被java.ext.dirs系统变量所指定的路径中的所有类库。
3、应用程序类加载器
又称之为系统类加载器,是默认的类加载器,可以通过getSystemClassLoader()获取,负责加载用户路径上所指定的类库。
注意:除了JVM提供的三种类加载器外,用户还可以自定义类加载器,类加载器的关系图如下:
1、工作原理
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,请求最终到达最顶层的启动类加载器中,如果父类加载器能够完成加载任务,就成功返回,否则子加载器才会自己尝试去加载。
2、优点
类会随着它的类加载器一起具备带有优先级的层次关系,可保证java程序的稳定运行;实现简单,所有实现代码都集中在java.lang.ClassLoader的loadClass()中。