java类加载机制

摘要

本文将详细介绍java的类加载机制,包括加载、验证、准备、解析和初始化五个阶段,并且介绍java加载中的双亲委托机制。并结合实际的案例进行剖析。并特意区分了java类加载和java对象的创建过程。

0x00、类加载:从字节流.classjava Class

我们都知道,java是面向对象的语言,程序的设计主要依靠各个对象进行交互完成,而在执行时,我们也是用使用在内存中实际的一个个对象。然而我们都知道的是,当完成编译以后,class文件实际上以字节流存储在硬盘上的(一般情况下),java虚拟机是如何将字节流文件转化为java Class的?注意这里请不要将类加载与java对象的创建混淆,类加载是java对象创建的前置工作。因为不管自定义的类还是java自定义的类,我们在使用这些类的实例化对象时,其最终只不过是java堆内存中的一块数据区域而已(详情请见java中的对象表示机器创建),但如何最终分配这些内存,以及如何将类和方法绑定,我们必须为java class字节流(实际上是java类的字节表示)创建虚拟机能够理解的数据结构。

0x01、加载:不仅仅是加载

需要注意的是,这里的加载和本文提到的类加载不是相同的含义,这里的加载是指完成从java字节流文件到jvm虚拟机中class的数据结构表示。虚拟机在实现时,为了灵活性,将其充分的解耦,把加载又分成了以下三个步骤:

  1. 根据类的全限定名加载字节流文件
  2. 将字节流转换为虚拟接方法区可理解的运行时数据结构
  3. 在内存中生成一个代表这个类的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);
}

我们阐述一下双亲委托机制的工作过程:

  1. 当前加载器收到加载请求时,不是直接自己进行加载工作,而是交给父类加载器进行加载
  2. 如果父类加载器仍然存在父类,那么依次递归上述步骤,直到遇到顶层启动类加载器
  3. 如果父类加载器可以返回结果,那么便成功返回,否则使用自己的加载器完成加载过程。

其示意图如下所示:

代码如下:

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;
}

posted @ 2020-06-27 11:42  smalllll  阅读(164)  评论(0编辑  收藏  举报