深入java虚拟机之类加载器

     2022-04-08     301

关键词:

前言:
虚拟机设计团队把类加载阶段中"通过一个类的权限定名来获取描述此类的二进制字节流"这个动作放到虚拟机外部区实现,让程序自己决定如何去获取所需的类。实现这个动作的代码模块就被称为类加载器

它最初是为了满足Java Applet的需求而被开发,而现在Java Applet基本已经宣布死亡,但类加载器却在类层次划分、OSGi、热部署、代码加密等领域大放异彩。它是java技术体系中一块重要的基石。


对于任意一个类,都需要由加载该类的加载器和这个类本身一同确立在虚拟机中的唯一性。通俗说就是:比较两个类是否相等,这两个类必须是同一个类加载器的前提才有意义。否则,就算两个类是来源同一个class文件,只要加载器不同,那么这两个类必定是不相等的。

备注:
这里所指的"相等", 包括:Class对象的equals方法、isAssignableForm方法、isInstance方法的返回结果,也包括instanceof关键字做对象所属关系判定等情况。

双亲委派模型

在虚拟机的角度看,只有两类类加载器:启动类加载器、其他类加载器。启动类加载器是属于虚拟机中的一部分,是用C++实现的,而其他类加载器则是java实现的。在开发者眼中看来,类加载器可以分的更加细致些:
启动类加载器:
这个类加载器,负责把放在JAVA_HOMElib目录中的,或者被-Xbootclasspath参数指定的路径中的,并且是虚拟机识别的类库加载到虚拟机中。启动类加载器无法被java程序直接引用。

扩展类加载器:
它是负责JAVA_HOMElibext目录中的,或者被java.ext.dirs系统变量指定的所有类库,开发者可以直接使用扩展类加载器。

应用程序类加载器:
这个类加载器是由sun,misc.Launcher$AppClassLoader实现的。因为这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般都会称为系统类加载器。它负责加载用户类路径上的(ClassPath)上所指定的类库。开发者可以直接使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般这个就是程序中默认的类加载器。

备注:
我们的应用程序都是由这三种类加载器相互配合进行加载的,如果有必要,还可以加入自己的类加载器。类加载器与自定义类加载器之间的关系如下图:
技术分享图片

双亲委派模型,规定了,每一个类加载器(除了启动类加载器)都必须有父类,并且这种关系不是继承实现,而是通过组合实现的,而且要求当调用了当前类加载器时,必须先给父类去加载,如果没有父类,那么就交给启动类加载器加载,如果父类返回无法加载,则给当前类加载器加载,如果当前也无法加载,则抛出ClassNotFoundException.

这样做的好处,就会有一个优先级,确保一个类只会被一个类加载器加载如虚拟机中,如果都是优先当前,那么就会可能出现同一个类在虚拟机中被不同的类加载器加载了多遍,并且这多个类还是不相等的。

双亲委派的代码都在java.lang.ClassLoader的loadClass方法中,我们可以通过分析源代码就可以知道:

/**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     *
     * <ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
     *   has already been loaded.  </p></li>
     *
     *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
     *   on the parent class loader.  If the parent is <tt>null</tt> the class
     *   loader built-in to the virtual machine is used, instead.  </p></li>
     *
     *   <li><p> Invoke the {@link #findClass(String)} method to find the
     *   class.  </p></li>
     *
     * </ol>
     *
     * <p> If the class was found using the above steps, and the
     * <tt>resolve</tt> flag is true, this method will then invoke the {@link
     * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
     *
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
     * #findClass(String)}, rather than this method.  </p>
     *
     * <p> Unless overridden, this method synchronizes on the result of
     * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
     * during the entire class loading process.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @param  resolve
     *         If <tt>true</tt> then resolve the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class could not be found
     */
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

破坏双亲委派模型
双亲委派模型不是一个强制性的约束模型,只是java设计者们推荐给开发者的类加载器实现方式。绝大部分都是遵循这个模型的。但截止到深入java虚拟机作者编写这本书时就已经有三次破坏该模型。

第一次:
是出现在双亲委派模型之前,因为jdk1.2之后才引入双亲委派模型。而类加载器和抽象类java.lang.ClassLoader早在jdk1.0就出现了。为了兼容双亲委派模型之前的类加载器,java设计团队不得不妥协,给jdk1.2之后的java.lang.ClassLoader类新添加了一个protected的findClass方法,在此之前,用户去继承java.lang.ClassLoader就是为了重写loadClass()方法,因为虚拟机在进行类加载的时候会调用类加载器的 一个私有方法loadClassInternal(),而此方法唯一逻辑就是调用自己的loadClass()方法。前面通过loadClass源码,我们可以知道如果父类加载失败,才会去调用findClass()方法定义的类加载过程。这样就可以保证满足双亲委派模型。

第二次:
第二次是因为双亲模型自身的缺陷,虽然双亲委派模型很好的解决了各个类加载器的基础类的统一,但如果基础类又要调用回用户的代码呢?比如说:JDNI服务,JDNI服务是java的标准服务,它的代码是有启动类加载器加载,但JDNI的目的是为了对资源进行集中管理和查找,它需要调用其他独立厂商实现并部署在应用程序的ClassPath下的JDNI接口提供者的代码,但启动类并不认识这些代码。为了解决这类问题,java设计团队,提供了一种并不优雅的设计:线程上下文类加载器。这个类加载器可以通过java.lang.Thread类的setContextCLassLoader()方法进行设置,如果没有创建线程时设置,它会从父线程中继承一个,如果应用程序全局都没有设置,那么这个类加载器就是应用程序类加载器。

JDNI服务使用了这个线程上下文加载器去加载所需的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作。java中设计SPI的加载动作基本都采用这种方式,比如:JDNI、JDBC、JAXB、JCE和JBI等。

第三次:
第三次是由于用户对程序动态性的追求导致的,这里说的动态性是指:代码热替换、模块热部署等,说白了就是希望应用程序能像电脑外设那样,插上鼠标,键盘,不用重启机器。热部署对于企业生产环境的诱惑是毋庸置疑的。
技术分享图片

深入java虚拟机之类加载机制

前言:前面学习了类Class文件格式和里面具体的内容,也已经学习了运行时数据区的各部分区域的内容。接下来就是学习JVM是如何把Class文件中记录的信息加载到运行时内存中的,以及class文件中各个部分的信息分别存放在运行时... 查看详情

深入java虚拟机-类加载器

此系列为深入java虚拟机(周志明著)学习笔记通过一个类的全限定名来获取描述此类的二进制字节流的代码模块称为类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性。通... 查看详情

深入理解java虚拟机——类加载器

目录一、类加载器的概述二、类与加载器的概述三、类加载器的分类四、自定义类加载器并判断两个类是否相等的示例4.1自定义类加载器的步骤4.2自定义类加载器并判断两个类是否相等的代码示例五、双薪委派模型5.1、双薪委派... 查看详情

jvm之类加载器加载过程及双亲委派机制

JVM的生命周期虚拟机的启动Java虚拟机的启动是通过引导类加载器(bootstrapclassloader)创建一个初始类(initialclass)来完成的,这个类是由虚拟机的具体实现指定的。虚拟机的执行一个运行中的Java虚拟机有着一个清晰的任务:执... 查看详情

jvm之类加载器加载过程及双亲委派机制

JVM的生命周期虚拟机的启动Java虚拟机的启动是通过引导类加载器(bootstrapclassloader)创建一个初始类(initialclass)来完成的,这个类是由虚拟机的具体实现指定的。虚拟机的执行一个运行中的Java虚拟机有着一个清晰的任务:执... 查看详情

深入理解java虚拟机——类加载机制(代码片段)

文章目录类加载机制类的生命周期类的加载过程1、加载2、验证3、准备4、解析5、初始化类的初始化时机类加载器类与类加载器类加载器分类双亲委派模型工作过程源码分析双亲委派机制的好处类加载机制类的生命周期一个类型... 查看详情

深入理解java虚拟机--虚拟机类加载机制(代码片段)

...4.双亲委派机制4.1双亲委派机制说明4.2好处本文参考于《深入理解Java虚拟机》1.虚拟机类加载机制说明Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接... 查看详情

深入理解java虚拟机

title:深入理解Java虚拟机date:2020-05-1410:58:24tags:JVM,虚拟机目录title:深入理解Java虚拟机date:2020-05-1410:58:24tags:JVM,虚拟机1.运行时数据区域2.GC垃圾回收3.内存分配与回收策略4.类加载机制1.加载2.验证3.准备4.解析5.初始化5.类与类加载器1.... 查看详情

6类加载器深入解析与重要特性剖析

6.1、类加载器介绍  类加载器用来把类加载到java虚拟机中。从JDK1.2版本开始,类的加载过程采用“双亲委托机制”,这种机制能更好的保证java平台的安全。在“双亲委托机制”中,除了java虚拟机自带的根类加载... 查看详情

深入理解java虚拟机——类加载器(代码片段)

目录一、类加载器的概述二、类与加载器的概述三、类加载器的分类四、自定义类加载器并判断两个类是否相等的示例4.1自定义类加载器的步骤4.2自定义类加载器并判断两个类是否相等的代码示例五、双薪委派模型5.1、双薪委派... 查看详情

深入java虚拟机之字节码执行引擎

前言:class文件结构、类加载机制、类加载器、运行时数据区这四个java技术体系中非常重要的知识,学习完了这些以后,我们知道一个类是通过类加载器加载到虚拟机,存储到运行时数据区,而且我们也知道了我们方法体内的代... 查看详情

深入理解jvm(③)虚拟机的类加载器(双亲委派模型)(代码片段)

前言先解释一下什么是类加载器,通过一个类的全限定名来获取描述该类的二进制字节流,在虚拟机中实现这个动作的代码被称为“类加载器(ClassLoader)”。类与类加载器类加载器虽然只用于实现类的加载动作,但它在Java程序... 查看详情

《深入理解java虚拟机》第三版第七章要点总结(代码片段)

本文仅作为复习清单使用类生命周期加载验证准备解析初始化使用卸载常量优化常量传播常量折叠类的加载通过全限定名获取二进制字节流将静态存储结构转化为方法区的运行时数据结构生成Class对象验证过程(可关闭)文件格式... 查看详情

jvm之类加载classloader

类加载子系统是jvm虚拟机的重要组成部分 自定义classLoader 系统类加载器按照层次,分为: (1).启动类加载器(BootstrapClassLoader):将加载/JAVAHOME/lib以及为-Xbootclasspath所指定的目录下的类库,是核心JavaAPI的class文件,用于... 查看详情

深入理解java虚拟机类加载机制

本文内容来源于《深入理解Java虚拟机》一书,非常推荐大家去看一下这本书。本系列其他文章:【深入理解Java虚拟机】Java内存区域模型、对象创建过程、常见OOM【深入理解Java虚拟机】垃圾回收机制1、类加载机制概述虚拟机把... 查看详情

《深入理解jvm——类加载器》

JVM深入理解JVM(6)——类加载器 PostedbyCrowonAugust21,2017虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流(即字节码)”这个动作放到Java虚拟机外部去实现,以便让应用程序自... 查看详情

java虚拟机之类加载机制

...字节流,无论是以何种形式存在的都可以。1.引言java类被虚拟机编译之后成为一个Class的字节码文件,该字节码文件中包含各种描述信息,最终都需要加载到虚拟机中之后才能运行和使用。那么虚拟机是如何加载这些Class文件?Cl... 查看详情

jvm虚拟机之类加载的过程

...解释,感觉好像理解但是自己却无法说清楚,今天看了《深入理解JVM虚拟机》一书的讲解感觉自己有些透了,在此记录下来自己的理解~!类的生命周期类加载过程(主动|被动)类的主动引用(一定会发生类的初始化)-new一个类... 查看详情