jjzjj

【JVM】jvm的双亲委派机制

No8g攻城狮 2023-04-04 原文

双亲委派机制

一、JVM体系结构

我们先在这里放一张 JVM 的体系架构图,方便我们有个总体认知。

在了解JVM的双亲委派机制之前,你不得不需要知道的几个名字:本文我们只讲上图中里的类加载子系统下的三个阶段之一(Loading,加载阶段)有关的内容,即下图中用红色线圈起来的几个名词。
引导类加载器(Bootstrap ClassLoader);
扩展类加载器(Extension ClassLoader);
应用类加载器(Application ClassLoader)。

有关其详细概念请移步至:【JVM】java的jvm类加载器和类加载子系统之JVM类加载器和类加载子系统里的加载阶段,在这里就不多说了。

二、双亲委派机制的含义

当一个类加载器收到了类加载的请求的时候,它不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。

Java中提供如下四种类型的加载器,每一种加载器都有指定的加载对象,具体如下:

  • Bootstrap ClassLoader(引导类加载器) :主要负责加载Java核心类库,%JAVA_HOME%/jre/lib/目录下,resources.jar或者sun.boot.class.path路径下的内容等。
  • Extention ClassLoader(扩展类加载器):主要负责从 java.ext.dirs 系统属性所指定的目录中加载类库,或从JDK的安装目录的**/jre/lib/ext/**子目录(扩展目录)下加载的类库。
  • Application ClassLoader(应用程序类加载器) :主要负责加载当前应用的classpath下的所有类。
  • User-Defined ClassLoader(用户自定义类加载器) : 用户自定义的类加载器,可加载指定路径的class文件。

注意:这里存在的加载器之间的层级关系并不是以继承的方式存在的,而是以组合的方式处理的。

这四种类加载器存在如下关系,当进行类加载的时候,虽然用户自定义类不会由 Bootstrap ClassLoader 或是 Extension ClassLoader 加载(由类加载器的加载范围决定),但是代码实现还是会一直委托到 Bootstrap ClassLoader, 上层无法加载,再由下层是否可以加载,如果都无法加载,就会触发 findclass(),抛出 classNotFoundException

三、双亲委派机制的源代码

打开IDEA等代码开发工具,搜索 ClassLoader 并进入类中,找到 loadClass() 方法,源代码如下:

    /**
     * Loads the class with the specified binary name. 
     * This method searches for classes in the same manner as the loadClass(String, boolean) method.  
     * It is invoked by the Java virtual machine to resolve class references.  
     * Invoking this method is equivalent to invoking #loadClass(String, boolean) loadClass(name,false).
     *
     * @param:  name The binary name of the class
     * @return:  The resulting Class object
     * @throws:  ClassNotFoundException If the class was not found
     */
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    /**
     * Loads the class with the specified binary name.  
     * The default implementation of this method searches for classes in the following order:
     *
     * 		1. Invoke #findLoadedClass(String) to check if the class
     *   has already been loaded. 
     *   	2. Invoke the #loadClass(String) loadClass method
     *   on the parent class loader.  If the parent is null the class
     *   loader built-in to the virtual machine is used, instead.  
     *   	3. Invoke the #findClass(String) method to find the
     *   class. 
     * 
     * If the class was found using the above steps, and the
     * resolve flag is true, this method will then invoke the #resolveClass(Class) method on the resulting Class object.
     *
     * Subclasses of ClassLoader are encouraged to override 
     * #findClass(String), rather than this method. 
     *
     * Unless overridden, this method synchronizes on the result of
     * #getClassLoadingLock getClassLoadingLock method
     * during the entire class loading process.
     *
     * @param: name The binary name of the class
     * @param: resolve If true then resolve the class
     * @return: The resulting Class 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;
        }
    }

其实这段代码已经很好的解释了双亲委派机制,为了大家更容易理解,我做了一张图来描述一下上面这段代码的流程:
从上图中我们就更容易理解了,当一个 Hello.class 这样的文件要被加载时。不考虑我们自定义类加载器,首先会在 AppClassLoader 中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的 loadClass() 方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达 Bootstrap ClassLoader 之前,都是在检查是否加载过,并不会选择自己去加载。直到 Bootstrap ClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出 ClassNotFoundException。那么有人就有下面这种疑问了?

为什么为有这样的设计?下面我们再来说下这样设计的意义就明白了这样做的好处了。

四、双亲委派机制的意义

这种设计有个好处是:

  • 第一:避免类的重复加载。
  • 第二:保护程序的安全,防止核心API被随意篡改。

如果有人想替换系统级别的类,比如:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap ClassLoader 加载过了(因为当一个类需要加载的时候,最先去尝试加载的就是 Bootstrap ClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。

  1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
  2. 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
  3. 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
  4. 父类加载器一层一层往下分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常。

五、示例代码

现在有一个例子,在我们自定义的 String 类中(包名是 java.lang包下创建),写一个main方法,执行它,如下:

package java.lang;

public class String {

    static {
        System.out.println("这是自定义的String类!");
    }

    public static void main(String[] args) {
        String customClass = new String();
        System.out.println(customClass);
    }
}

执行后的结果如下:
可以看到,报错在java.lang.String中找不到类方法。因为String类是启动类加载器创建的,不是我们自定义的String类,故没有main方法。

完结!

有关【JVM】jvm的双亲委派机制的更多相关文章

  1. ruby - Ruby 是否提供响应 OS X 上的 Apple 事件的机制? - 2

    我正在使用Ruby-Tk为OSX开发一个桌面应用程序,我想为该应用程序提供一个AppleEvents接口(interface)。这意味着应用程序将定义它将响应的AppleScript命令的字典(对应于发送到应用程序的Apple事件),并且用户/其他应用程序可以使用AppleScript命令编写Ruby-Tk应用程序的脚本。其他脚本语言支持此类功能——Python通过位于http://appscript.svn.sourceforge.net/viewvc/appscript/py-aemreceive/的py-aemreceive库和Tcl通过位于http://tclae.source

  2. ruby - Ruby 的方法解除绑定(bind)机制有什么意义? - 2

    Method#unbind返回对该方法的UnboundMethod引用,稍后可以使用UnboundMethod#bind将其绑定(bind)到另一个对象.classFooattr_reader:bazdefinitialize(baz)@baz=bazendendclassBardefinitialize(baz)@baz=bazendendf=Foo.new(:test1)g=Foo.new(:test2)h=Bar.new(:test3)f.method(:baz).unbind.bind(g).call#=>:test2f.method(:baz).unbind.bind(h).

  3. Selenium等待机制之显示等待 - 2

    显示等待需要用到两个类:WebDriverWait和expected_conditions两个类WebDriverWait:指定轮询间隔、超时时间等expected_conditions:指定了很多条件函数(也可以自定义条件函数)具体可以参考官网:selenium.webdriver.support.expected_conditions—Selenium4.5documentationfromseleniumimportwebdriverfromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimpor

  4. ruby - Scala 的扩展性是否优于其他 JVM 语言? - 2

    这是我目前知道的唯一询问方式。据了解,Scala使用Java虚拟机。我以为Jruby也是。Twitter将其中间件切换为Scala。他们可以做同样的事情并使用Jruby吗?他们是否可以从Jruby开始,而不是因为扩展问题导致他们首先从Ruby迁移到Scala?我不明白Jruby是什么吗?我假设因为Jruby可以使用Java,所以它可以扩展到Ruby不能的地方。在这种情况下,一切都归结为静态类型与动态类型吗? 最佳答案 Scala是“可扩展的”,因为语言可以通过库进行改进,使扩展看起来像是语言的一部分。这就是为什么actors看起来像

  5. ruby - 不支持您提供的授权机制。请使用 AWS4-HMAC-SHA256 - 2

    我收到错误AWS::S3::Errors::InvalidRequest不支持您提供的授权机制。请使用AWS4-HMAC-SHA256.当我尝试将文件上传到新法兰克福地区的S3存储桶时。所有适用于USStandard区域。脚本:backup_file='/media/db-backup_for_dev/2014-10-23_02-00-07/slave_dump.sql.gz's3=AWS::S3.new(access_key_id:AMAZONS3['access_key_id'],secret_access_key:AMAZONS3['secret_access_key'])s3_

  6. Qt 中的信息输出机制:QDebug、QInfo、QWarning、QCritical 的简单介绍和用法 - 2

    Qt中的信息输出机制介绍QDebug在Qt中使用qDebug输出不同类型的信息浮点数:使用%!f(MISSING)格式化符号输出浮点数布尔值:使用%!(MISSING)和%!(MISSING)格式化符号输出布尔值对象:使用qPrintable()函数输出对象的信息qInfoqWarningqCritical自定义信息输出格式不同输出方式的区别和底层逻辑总结介绍在Qt中,信息输出机制用于在程序运行时输出各种信息,包括调试信息、提示信息、警告信息和错误信息等。Qt提供了多种信息输出机制,主要包括以下几种:qDebug:最常用的信息输出机制,用于输出各种调试信息,例如变量的值、函数的返回值和对象的状

  7. javascript - 如何有效地使用日志记录机制? - 2

    我正在使用log4javascript来记录和跟踪我的JavaScript代码中的问题。我以前见过类似的日志记录辅助工具,但我很难理解应该如何使用这些日志级别中的每一个才能更有用和更有成效。大多数时候,我最终会记录调试、信息或跟踪,但并没有真正意识到它们各自的效率如何。随着代码变得越来越大,它变得越来越困难,我觉得日志麻烦多于帮助。有人可以给我一些指南/帮助,以便我可以很好地使用日志记录机制。以下是log4javascript支持的不同日志级别:log4javascript.Level.ALLlog4javascript.Level.TRACElog4javascript.Level.

  8. Cookie/Session 的机制与安全 - 2

    文章目录Cookie的实现机制Cookie的安全隐患Cookie防篡改机制Session的实现机制Cookie和Session是为了在无状态的HTTP协议之上维护会话状态,使得服务器可以知道当前是和哪个客户在打交道。本文来详细讨论Cookie和Session的实现机制,以及其中涉及的安全问题。因为HTTP协议是无状态的,即每次用户请求到达服务器时,HTTP服务器并不知道这个用户是谁、是否登录过等。现在的服务器之所以知道我们是否已经登录,是因为服务器在登录时设置了浏览器的Cookie!Session则是借由Cookie而实现的更高层的服务器与浏览器之间的会话。Cookie是由网景公司的前雇员Lo

  9. javascript - node.js 中是否存在超时事件的通用机制? - 2

    我正在学习node.js,我能找到的大多数示例都是处理简单示例的。我更感兴趣的是构建真实世界的复杂系统,并评估node.js基于事件的模型如何处理真实应用程序的所有用例。我想应用的一个常见模式是让阻塞执行超时,如果它没有在特定超时时间内发生。例如,如果执行一个数据库查询需要超过30秒,那么对于某些应用程序来说可能太多了。或者如果读取一个文件需要超过10秒。对我来说,带超时的理想程序流与带异常的程序流类似。如果某个事件没有在某个预定义的超时限制内发生,那么事件监听器将从事件循环中清除,并且会生成一个超时事件。此超时事件将有一个备用监听器。如果事件被正常处理,那么超时监听器和事件监听器都会

  10. javascript - JavaScript WebSockets API 的机制 - 2

    我一直在尝试理解一些用于打开websocket的代码:varws=newWebSocket('ws://my.domain.com');ws.onopen=function(event){...}我的问题是握手是如何开始的?如果它是在WebSocket构造函数中启动的,那么如果到那时还没有设置,如何调用onopen呢?如果WebSocket构造函数创建一个执行握手的线程,那么在握手结束之前是否必须足够快地定义onopen?如果是这样,那听起来有点危险,因为如果JS虚拟机变慢,握手可能会在定义onopen之前完成,这意味着事件没有得到处理。还是设置onopen函数触发握手?有人可以向我解

随机推荐