java类加载机制
摘要
本文将详细介绍java的类加载机制,包括加载、验证、准备、解析和初始化五个阶段,并且介绍java加载中的双亲委托机制。并结合实际的案例进行剖析。并特意区分了java类加载和java对象的创建过程。
0x00、类加载:从字节流.class
到java Class
我们都知道,java是面向对象的语言,程序的设计主要依靠各个对象进行交互完成,而在执行时,我们也是用使用在内存中实际的一个个对象。然而我们都知道的是,当完成编译以后,class文件实际上以字节流存储在硬盘上的(一般情况下),java虚拟机是如何将字节流文件转化为java Class的?注意这里请不要将类加载与java对象的创建混淆,类加载是java对象创建的前置工作。因为不管自定义的类还是java自定义的类,我们在使用这些类的实例化对象时,其最终只不过是java堆内存中的一块数据区域而已(详情请见java中的对象表示机器创建),但如何最终分配这些内存,以及如何将类和方法绑定,我们必须为java class字节流(实际上是java类的字节表示)创建虚拟机能够理解的数据结构。
0x01、加载:不仅仅是加载
需要注意的是,这里的加载和本文提到的类加载不是相同的含义,这里的加载是指完成从java字节流文件到jvm虚拟机中class的数据结构表示。虚拟机在实现时,为了灵活性,将其充分的解耦,把加载又分成了以下三个步骤:
- 根据类的全限定名加载字节流文件
- 将字节流转换为虚拟接方法区可理解的运行时数据结构
- 在内存中生成一个代表这个类的
java.lang.Class
对象,作为方法区这个类的数据访问入口
其中,第一步虚拟机并没有做出详细的规定,因此这就给java提供了极大的灵活性和扩展性,比如以下几种读取字节流方式相信大家都不陌生:
- 从zip包中读取,比如本地文件的JAR包、WAR包。这也是最常见的方式。
- 从网络中获取,这就是之前的applet基础
- 运行时计算生成,使用最多的就是动态代理技术。
上述第三步的目的是为了访问实际内存中的实例数据,因为实际的对象是分配在堆中的,而方法区中要想访问这些数据,必须借助这个java.lang.Class
对象的作用。
0x02、验证:确保虚拟机的安全
验证的大致作用是了确保Class文件中的字节流符合当前虚拟机技术的要求,同时不会危害虚拟机自身的安全。
校验大致分为如下几部分:文件格式验证、元数据验证、字节码验证、符号引用验证。因为这部分java对java开发人员是透明的,所以我们不重点展开讨论了。
0x03、准备:静态变量的赋值
准备阶段主要是正式为类变量分配内存并设置类变量初始值的阶段(实际上就是所有基本类型的零值),这些变量所使用的内存都将在方法区中进行分配。需要注意的是,这里分配的内存仅仅包含类的静态变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。
0x04、解析:符号引用到直接引用
0x05、初始化:字节码的执行起点
由于在前期的准备阶段,只是为类变量分配了内存以及分配了内存,并且其值都是系统零值,这个阶段才真正的执行诸如下面的代码:
private static int CERTAIN_INT_CONST = 100;
private static boolean CERTAIN_BOOL_CONST = true;
static {
CERTAIN_BOOL_CONST = true;
}
这些语句会自动的被编译器所收集,然后虚拟机开始执行开发人员所定制的初始化,需要注意的是,static变量的初试化也是遵循继承中初始化机制的,即父类的初始化在子类的初始化之前。
0x06、双亲类加载机制:
双亲委托机制是java类加载的一种推荐机制,简单来讲,虚拟机根据不同层次的类设计了不同层次的类加载器,这些类加载器构成了树结构的父子层次关系,而且这些父子关系是通过组合来完成的,也就是子加载器对父加载器进行了持有(相当于树模型中的子节点具有指向父节点的指针,这样做的好处是从叶子节点能够方便的访问到根节点),结构如下:
public class childClassLoader {
ClassLoader parent;
CLass<?> loadClass(String name,boolean reslove);
}
我们阐述一下双亲委托机制的工作过程:
- 当前加载器收到加载请求时,不是直接自己进行加载工作,而是交给父类加载器进行加载
- 如果父类加载器仍然存在父类,那么依次递归上述步骤,直到遇到顶层启动类加载器
- 如果父类加载器可以返回结果,那么便成功返回,否则使用自己的加载器完成加载过程。
其示意图如下所示:
代码如下:
protected synchronized Class<?> loadClass(String name,boolean resolve) throws
ClassNotFoundException {
//检查是否已经加载过
Class c = findLoadClass(name);
//该类未进行加载,那么执行加载
if(c == null) {
try{
//如果存在父类加载器,则进行递归加载
if (parent != null) {
c = parent.loadClass(name,false);
} else {
//如果不存在,那么再去执行顶层加载器
c = findBootstrapClassOrNull(name);
}
catch(ClassNotFoundException e) {
//父类加载器无法加载,那么一定会抛出
//ClassNotFoundException
}
//父类加载器无法加载,使用自己的加载器进行加载
if (c == null) {
c = findClass(name);
}
}
}
return c;
}