Java类加载简介
类的生命周期JVM把class文件加载的内存,并对数据进行校验、转换解析和初始化,最终形成JVM可以直接使用的Java类型的过程就是加载机制。类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸
类的生命周期
JVM把class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成JVM可以直接使用的Java类型的过程就是加载机制。
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称链接。
加载(装载)、验证、准备、初始化和卸载这五个阶段顺序是固定的,类的加载过程必须按照这种顺序开始,而解析阶段不一定;它在某些情况下可以在初始化之后再开始,这是为了运行时动态绑定特性。值得注意的是:这些阶段通常都是互相交叉的混合式进行的,通常会在一个阶段执行的过程中调用或激活另外一个阶段。
加载
什么情况下需要开始类加载过程中的第一个阶段(加载)呢?虚拟机规范中并没有强行的约束,而是由虚拟机的具体实现自由把握。
在加载阶段,虚拟机需要完成以下三件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
二进制字节流可以来自本地的class文件或者Jar包、Zip包、War包等等,也可以来自网络。
连接
连接为类的初始化做准备。
初始化
对于初始化阶段,虚拟机规范严格规定了有且只有四种情况必须立即对类进行初始化(而加载、验证、准备自然需要在此之前开始):
- 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条字节码指令最常见的Java代码场景分别是:使用new关键字实例化对象、读取一个类的静态字段(被final修饰、已在编译阶段把结果放入常量池的静态字段除外)、设置一个类的静态字段、调用一个类的静态方法。
- 使用java.lang.reflect包的方法对类进行反射调用(例如Class.forName(className)、class.newInstance()、method.invoke(obj, args...)等等)的时候,如果类没有进行过初始化,则需要先触发其初始化。
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动的时候,用户需要指定一个执行主类(含有main方法的类),虚拟机先初始化这个主类。
这四个场景中的行为称为对一个类进行主动引用。除此之外所有对类的引用方式,都不会触发初始化,称为被动引用。被动引用是否会触发类初始化之前(加载、验证)的动作,则视具体的情况和虚拟机的具体实现而定。下面五个例子说明被动引用。
(1)代码:
package com.zzj.classloader;
public class LoadTest {
public static void main(String[] args) {
System.out.println(Son.value);
}
}
class Father {
public static String value = "Father";
static {
System.out.println("Father init!");
}
}
class Son extends Father {
static {
System.out.println("Son init!");
}
}
输出:
Father init!
Father
结果并没有输出"Son init!",只输出了"Father init!"。这说明通过子类去引用父类的静态字段,不会触发子类的初始化,而会触发父类的初始化。至于是否会触发子类初始化之前(加载、验证)的动作,则视虚拟机的具体实现而定。
(2)代码:
package com.zzj.classloader;
public class LoadTest {
public static void main(String[] args) {
A[] a = new A[1];
System.out.println(a.length);
}
}
class A{
static{
System.out.println("A init!");
}
}
结果没有输出“A init!”!也就是说没有触发类A的初始化。但是这段代码触发了另一个名为“[Lcom.zzj.classloader.A”类的初始化。这是一个由虚拟机自动生成并且继承自Object的子类,创建动作由指令newarray触发。
(3)代码:
package com.zzj.classloader;
public class LoadTest {
public static void main(String[] args) {
System.out.println(A.value);
}
}
class A{
public static final String value = "A";
static{
System.out.println("A init!");
}
}
结果没有输出“A init!”!也就是说没有触发类A的初始化。这是因为类A的常量value在编译阶段就已经放入了类LoadTest的常量池,编译完成后,类LoadTest和类A已经没有任何关系了。
(4)代码:
package com.zzj.classloader;
public class LoadTest {
public static void main(String[] args) {
Class<?> clazz = A.class;
System.out.println(clazz.getName());
}
}
class A {
static {
System.out.println("A init!");
}
}
结果没有输出“A init!”!也就是说没有触发类A的初始化。但是显然触发了类A的加载,否则无法得到类A的Class对象。
(5)代码:
package com.zzj.classloader;
public class LoadTest {
public static void main(String[] args) throws Exception {
Object object = new Object();
boolean b = object instanceof B;
System.out.println("test:" + b);
}
}
class B {
static {
System.out.println("B init!");
}
}
结果没有输出“B init!”!也就是说没有触发类B的初始化。这说明类型推断不会触发类的初始化,但会触发类的加载,否则无法判断对象是否属于该类型。另外强制转换也与类型推断同理:
Object object = new Object();
B b = (B) object;
类初始化是类加载过程的最后一步。到了类初始化阶段,才真正开始执行类中定义的Java程序代码。
类加载器
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块被称为“类加载器”。
由来
类加载器可以说是Java语言的一项创新,也是Java语言流行的重要原因之一,它最初是为了满足Java Applet的需求而被开发出来的。如今Java Applet已基本绝迹,但类加载器却在类层次划分、OSGI、热部署、代码加密等领域大放异彩,成为Java技术体系中一块重要的基石,可谓失之桑榆,收之东隅。
分类
Java内置了三个类加载器:
•引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,对应目录为$JAVA_HOME\jdk\jre\lib。该类加载器是用原生代码来实现的,不是由Java代码实现。
•扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录(JAVA_HOME\jdk\jre\lib\ext)。该类加载器在此目录里面查找并加载Java 类。
•系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。
每一个类加载器都对应一个类路径,每一个类加载器都只会加载自己所对应的类路径下的Java类。
除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过给出的 getParent()方法可以得到。对于系统类加载器来说,其父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此【类加载器 Java 类】的类加载器。因为类加载器 Java类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根节点就是引导类加载器。
需要注意的是,类加载器的这种继承关系并不是Java语法上的继承关系!语法上的继承关系如下(jdk1.6):
ExtClassLoader和AppClassLoader都是sun.misc.Launcher类的内部类,Launcher类位于Java核心库rt.jar中。
引导类加载器不是由Java语言编写的,因此不在此继承体系内。
更多推荐
所有评论(0)