【JVM】双亲委派机制详解

    通过上篇文章我们学习了类加载,也提到了因为双亲委派机制的存在自定义类加载器的实现,不要去覆盖ClassIoader类的loadClass方法,去实现findClass方法,接下来详细解释一下双亲委派机制,干货满满,耐心看完

双亲委派机制

什么是双亲委派机制

在这里插入图片描述

为什么要使用双亲委托这种模型呢?

    考虑到安全因素,因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。

    比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象。

    另外我们还可以试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。

如何判定两个class是相同?

    同一个class文件,被不同的类加载器加载,会创建不同的class对象JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。
    Proxy.newProxyInstance(Class loader)第一个参数就是类加载器

双亲委派机制源码

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 ifclass not found
                 // from the non-null parent class loader
             }
            if (c == null) {
                 // If still not found, then invokefindClass in order
                 // to find the class.
                 long t1 = System.nanoTime();
                 //此findclass方法在jdk1.2之前没有
                 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;
     }
 }

破坏双亲委派模型

    双亲委派机制的破坏,在JDK发展的过程中,是发生了多次:

第一次破坏

    在jdk1.2之前,那时候还没有双亲委派模型,不过已经有了ClassLoader这个抽象类,所以已经有人继承这个抽象类,重写loadClass方法来实现用户自定义类加载器。

    而在1.2的时候要引入双亲委派模型,为了向前兼容,loadClass 这个方法还得保留着使之得以重写,新搞了个findClass方法让用户去重写,并呼吁大家不要重写loadClass贝要重写findClass。

    这就是第一次对双亲委派模型的破坏,因为双亲委派的逻辑在loadClass上,但是又允许重写loadClass, 重写了之后就可以破坏委派逻辑了。

第二次破坏

    双亲委派机制,是种自上而下的加载需求,越往上类越基础。在实际的应用中双亲委派解决了java基础类统一加载的问题,但是却着实存在着一定的缺陷。jdk中 的基础类作为典型的api被用户调用,但是也存在被api调用用户代码的情况,典型的如SPI代码。

SPI机制简介

SPI的全名为Service Provider Interface,主要是应用于厂商自定义组件或插件中。
在java.util.ServiceLoadet的文档 里有比较详细的介绍。简单的总结下java SPI机制的思想:

我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计
里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的
原则,如果需要替换一一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一一种服务发
现机制。Java SPI就是提供这样的一一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到
程序之外,在模块化设计中这个机制尤其重要。
DriverManager源码:
static {
     loadInitialDrivers();
     println("JDBC DriverManager initialized");
 }
 
 private static void loadInitialDrivers() {
     String drivers;
     try {
         drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
             public String run() {
                 return
                System.getProperty("jdbc.drivers");
             }
         });
     } catch (Exception ex) {
         drivers = null;
    }

    AccessController.doPrivileged(new PrivilegedAction<Void>() {
         public Void run() {
             ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
             Iterator<Driver> driversIterator =  loadedDrivers.iterator(); 
             try{
                 while(driversIterator.hasNext()) {
                     driversIterator.next();
                 }
             } catch(Throwable t) {
                 // Do nothing
             }
             return null;
        }
     });
 
     println("DriverManager.initialize: jdbc.drivers = " + drivers);
 
     if (drivers == null || drivers.equals("")) {
         return;
     }
     String[] driversList = drivers.split(":");
     println("number of Drivers:" +  driversList.length);
     for (String aDriver : driversList) {
         try {
             println("DriverManager.Initialize: loading" + aDriver);
             Class.forName(aDriver, true,
             ClassLoader.getSystemClassLoader());
         } catch (Exception ex) {
             println("DriverManager.Initialize: loadfailed: " + ex);
         }
     }
}

在这里插入图片描述
    线程上下文加载器,应对特殊情况
在这里插入图片描述
    如果出现SPI相关代码时,我们应该如何解决基础类去加载用户代码类呢?
    这个时候,JVM不得不妥协,推出线程上下文类加载器的概念,去解决该问题。

线程上下文类加载器

(Thread Context ClassLoader)

设置线程_上下文源码

public Launcher() {
     Launcher.ExtClassLoader var1;
     // 扩展类加载器
     try {
         var1 = Launcher.ExtClassLoader.getExtClassLoader();
     } catch (IOException var10) {
         throw new InternalError("Could not create
        extension class loader", var10);
     }
     // 应⽤类加载器/系统类加载器
     try {
         this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
     } catch (IOException var9) {
         throw new InternalError("Could not create application class loader", var9);
     }
     // 线程上下⽂类加载器
     Thread.currentThread().setContextClassLoader(this.loader);
     String var2 = System.getProperty("java.security.manager");
     if (var2 != null) {
         SecurityManager var3 = null;
         if (!"".equals(var2) && !"default".equals(var2)) {
             try {
                 var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
             } catch (IllegalAccessException var5) {
             } catch (InstantiationException var6) {
             } catch (ClassNotFoundException var7) {
             } catch (ClassCastException var8) {
             }
         } else {
            var3 = new SecurityManager();
         }
         if (var3 == null) {
             throw new InternalError("Could not create SecurityManager: " + var2);
         }
         System.setSecurityManager(var3);
     }
 }
获取线程上下文源码:
 public static <S> ServiceLoader<S> load(Class<S> service) {
     ClassLoader cl = Thread.currentThread().getContextClassLoader();
     return ServiceLoader.load(service, cl);
 }

第三次破坏

    这次破坏是为了满足热部署的需求,不停机更新这对企业来说至关重要,毕竟停机是大事。OSGI就是利用自定义的类加载器机制来完成模块化热部署,而它实现的类加载机制就没有完全遵循自下而上的委托,有很多平级之间的类加载器查找。

OSGi: Bundle生命周期
在这里插入图片描述
在上图中,人工转换箭头线上的install、update、resolve、 start、 stop是通过在控制台输入对应命令来触发。
Bundle生命周期过程之中的6种状分别为:

  1. UNINSTALLED (未安装) :状态值为整数1。此时Bundle中的资源是不可用的。
  2. INSTALLED (已安装) :状态值为整数2。此时Bundle已经通过了OSGi框架的有效性校验并分配了Bundle ID,本地资源已加载,但尚
    未对其依赖关系进行解析处理。
  3. RESOLVED (已解析) :状态值为整数4。此时Bundle已经完成了依赖关系解析并已经找到所有依赖包,而且自身导出的Package已
    可以被其它Bundle导入使用。在此种状态的Bundle要么是已经准备好运行,要么就是被停止了。
  4. STARTING (启动中) :状态值为整数8。此时Bundle的BundleActivator的start(方法已经被调用但是尚未返回。如果start0方法正常执
    行结束,Bundle将自动转换到ACTIVE状态;否则如果start(方法抛出了异常,Bundle将退回到RESOLVED状态。
  5. STOPPING (停止中) :状态值为整数16。此时Bundle的BundleActivator的stop0方法已经被调用但是尚未返回。无论stop0是正常
    结束还是抛出了异常,在这个方法退出之后,Bundle的状态都将转为RESOLVED。
  6. ACTIVE (已激活) :状态值为整数32。Bundle处 于激活状态,说明BundleActivator的start(方法已经执行完毕,如果没有其他动作,
    Bundle将继续维持ACTIVE状态。.就是被停止了。
OSGi: Bundle解析

Class Loader在Bundle被正确解析(状态变成Resolved)之后创建,而只有Bundle中的所有包约束条件都满足后,对应的Bundle才能被正确解析完毕。
Bundle的解析由定义在MANIFESTMF文件中的四个配置项定义:

  1. Import-Package: 定义当前Bundle需要导入的其它包(Package) ,- -般位于其它Bundle之中并且被定义为Export Package。
  2. Export-Package: 定义当前Bundle要导出的包。被导出的包中的类资源可以被其它Bundle导入,可以被定义到Import-Package中。
  3. Require Bundle:定义当前Bundle需要依赖的Bundle。
  4. DynamicImport- Package:定义需要动态导入的包。这里定义的包在
    Bundle解析过程中不会被使用到,而是会在运行时被动态解析并加载。

OSGi: Bundle-ClasPath
在MANIFEST.ME文件中定义的Bundle-Classpath会描述Classpath范围,告诉Classloader去哪里查找类。

下图是一个简单的样例:
在这里插入图片描述

OSGi:类加载机制

Bundle的加载策略、如何导入和导出代码是在OSGi规范的模块(Modules)层实现的。
OSGi框架对于每个Bundle (非Fragment Bundle)都会创建一个单独的类加载器(Class Loader),不过对于Class Loadet的创建可能会延迟 到真正需要它时才会发生。

Bundle的Class Loader搜索类资源的规则简要介绍如下:
    1.如类资源属于java.*包,则将加载请求委托给父ClassLoader;

    2.如类资源定义在OSGi框架的启动委托列表
        (usgi.frumework.bootdelegution)中,则将加载请求委托给父Cluss
        Loader;例如:

osgi. framework. bootdelegation=*
osgi. framework . bootdelegation=sun. *,com.sun.*

    3.如类资源属于在Import-Package中定义的包,则框架会将加载请求委托
        给导出此包的Bundle的Class Loader;

    4.如类资源属于在Require-Bundle中定义的Bundle,则框架会将加载请求
        委托给此Bundle的Class Loader;

    5. Bundle搜 索自己的Bundle-Classpath中定义的类资源;

    6. Bundle搜 索属于该Bundle的Fragment的类资源;

    7.判断是否找到导出(Export-Package)] 了对应资源,如果仍未找到进入动态导入查找;

    8.若类在DynamicImport- Package中定义,则开始尝试在运行环境中寻找符
        合条件的Bundle,框架会将加载请求委托给导出此包的Bundle的ClassLoader;

        注意DynamicImport-Package: *使用了星号来对所有资源进行通配,这也
        意味着对应的Bundle可以“看到”任何可能访问到的资源,这个选项应该尽量少地使用,除非别无它法。

    9. 如果在经过上面一-系列步骤后,仍然没有正确地加载到类资源,则OSGi框架会向外抛出类未发现异常。

总结

JVM的类加载机制,是自上而下的加载过程。

OSGi的类加载机制,既能在内部按照JVM的类机制机制去加载,
本身也会按照Bundle之间横向委托的方式进行类加载,所以它是- -种网状结构的类加载方式。

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
JVM中的双亲委派机制是一种类加载机制,它规定了在Java中一个类被加载时如何进行类加载器的选择。根据这个机制,当一个类需要被加载时,首先会由类加载器ClassLoader检查是否已经加载过该类,如果是,则直接返回已经加载过的类;如果不是,则将该请求委派给父类加载器去加载。这样的过程会一直向上委派,直到达到顶层的引导类加载器(Bootstrap ClassLoader)。引用 引用中提到,并不是所有的类加载器都采用双亲委派机制Java虚拟机规范并没有强制要求使用双亲委派机制,只是建议使用。实际上,一些类加载器可能会采用不同的加载顺序,例如Tomcat服务器类加载器就是采用代理模式,首先尝试自己去加载某个类,如果找不到再代理给父类加载器。 引用中提到,引导类加载器(Bootstrap ClassLoader)是最早开始工作的类加载器,负责加载JVM的核心类库,例如java.lang.*包中的类。这些类在JVM启动时就已经被加载到内存中。 综上所述,JVM双亲委派机制是一种类加载机制,它通过类加载器的委派方式来加载类,首先检查是否已经加载过该类,如果没有则委派给父类加载器去加载,直到达到顶层的引导类加载器。不过,并不是所有的类加载器都采用该机制,一些类加载器可能会采用不同的加载顺序。引用<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [JVM-双亲委派机制](https://blog.csdn.net/m0_51608444/article/details/125835862)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [jvm-双亲委派机制](https://blog.csdn.net/y08144013/article/details/130724858)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王某人@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值