• 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;
    }
    
    
  • 相关阅读:
    《代码整洁之道》之四 注释
    《代码整洁之道》之三 函数
    《代码整洁之道》之二 有意义的命名
    《代码整洁之道》
    Hibernate学习笔记
    Struts2复习笔记
    学习Spring必学的Java基础知识
    Eclipse下搭建Maven框架
    onvif实现
    rtmp服务端实现
  • 原文地址:https://www.cnblogs.com/zhangshoulei/p/13197829.html
Copyright © 2020-2023  润新知