Java类加载机制:探讨双亲委派模型的优缺点

前言 我们领导超级爱问双亲委派的缺点,优点大家都很容易记住,缺点是大家很容易忽略的!

摘要 在Java体系中,类加载是起着至关重要的作用,而双亲委派模型是一种旨在保障类加载安全与一致性的设计。然而,尽管双亲委派模型带来了很多优点,但也存在一些隐藏的弊端和需要注意的地方。让我们来了解下探讨双亲委派模型的利与弊,并通过通俗易懂的例子来说明。

什么是双亲委派模型

程序在加载的时候先向上委派(查找缓存),向下查找(加载路径)确保同一个类只会被加载一次,先去向上委派就是去查找缓存,如果自己缓存没有,所以提前加到缓存里面,可以不用向上委派,会向上查找直到加载到。如果在缓存中找到了,就不用再向上找了,说明已经加载了,直接返回。如果找到最上层(Bootstrap ClassLoder启动加载器)还是没有找到,这时会去向下加载自己路径进行查找,如果有这个类,直接返回,如果没有,向下查找就是加载路径里查找,如果仍然没有,就报不存在。

双亲委派模型的优点

  1. 避免重复加载: 双亲委派模型通过由上至下的加载方式,每个类都由一个唯一的全限定名和一个对应的类加载器,如果有两个类加载器分别加载了同一个类,JVM 会认为这两个类是不同的,从而导致类型转换异常等问题。通过双亲委派机制,父类加载器在加载类之前会先委托给自己的父类加载器去加载,从而保证一个类在 JVM中只会有一份,并且由其父类加载器所加载。

  2. 防范恶意代码: 我觉得这个是最重要的,如果没有双亲委派机制,我直接重写了一个带病毒的string类,那么你加载的就可能是我写的带病毒的string类,那不乱套啦,双亲委派模型限制了自定义加载器加载核心类的能力,有我也不加载我向上找,这有助于提高系统的安全性。就像一个普通员工想认识老板一样,他不会去同级别去找一个叫老板的人,他先会逐级向上找,一直找到公司最高级别那儿,找到顶层找到一个叫老板的人,他才认,每个想找老板的员工都是这个模式,那么找出来叫老板的人都是一个人。不会找到被冒充的老板。

    例子: 如果没有双亲委派模型,恶意代码可能会通过自定义加载器加载Java核心类,从而导致系统被操控、受损甚至瘫痪。

  3. 保持标准化: 双亲委派模型使得类加载过程更加标准化,所有加载器都遵循同样的加载规则。这就像在道路上行驶,所有车辆都要遵循相同的交通规则,确保了行驶的安全和顺畅。

双亲委派模型的缺点和注意事项

  • 双亲委派模型在某些情况下可能会受到一定限制。尽管这个模型在维护类加载的一致性和安全性方面表现出色,但在一些特殊场景下,我们可能需要更具弹性的加载方式。

    隔离性要求: 在某些情况下,应用程序可能需要加载同名类的不同版本,以实现不同功能或兼容不同的库。然而,双亲委派模型不适合处理这种需求,因为它会始终向上级加载器委派类加载请求,无法灵活地选择特定版本的类。

    例子: 假设你的应用需要同时使用两个版本的数据库驱动器,一个是旧版本,一个是新版本。由于双亲委派模型的限制,你无法在不同的类加载器中加载这两个版本的驱动器,可能导致冲突和错误。

    模块化系统需求: 应用系统逐步趋向于模块化系统,允许不同模块具有独立的类加载环境。然而,双亲委派模型在这方面可能不太适应,因为它要求类在整个加载链中具有唯一性。

    例子: 假设你正在开发一个模块化的插件系统,每个插件都是一个独立的模块,具有自己的类加载器。在这种情况下,双亲委派模型可能会导致不同插件之间的类冲突,限制了插件系统的灵活性。

    动态代码生成和加载: 一些应用需要在运行时动态生成和加载类,双亲委派模型可能不太适合这种需求。由于它要求类在类加载器链中已经存在,无法直接加载动态生成的类。

    例子: 如果你开发了一个支持插件的框架,允许用户在运行时编写和加载自定义插件。这时,双亲委派模型可能会变得不太灵活,因为新生成的类无法通过标准的委派机制加载。

  • 在这些情况下,我们需要更具灵活性的类加载策略。解决这些问题的一种方法是实现自定义的类加载器,覆写类加载逻辑,以适应特定需求。这样可以在一定程度上绕过双亲委派模型的限制,实现更灵活的加载方式。同时,一些现代的模块化框架,如OSGi,也提供了更为灵活的类加载和模块隔离机制,帮助解决了上述问题。


加载数据库驱动的性能折扣

  • 双亲委派模型在性能方面的折扣时,我们可以拿加载数据库驱动来举例。数据库驱动是Java应用程序与数据库之间的桥梁,负责在应用程序和数据库之间建立连接和通信。在传统的Java应用中,通常需要加载适当的数据库驱动程序以便访问数据库。然而由于双亲委派模型的存在,加载数据库驱动可能会导致一些性能开销。
    驱动加载流程: 假设你的应用程序需要加载一个特定的数据库驱动,比如MySQL驱动。按照双亲委派模型,当你的应用程序代码尝试加载这个驱动时,首先会从你的类加载器开始查找。如果找不到,它会向上级类加载器(通常是系统类加载器)发起加载请求,再依次向上查找,直到找到合适的类。
    类路径搜索: 当驱动加载时,每一级类加载器都需要在其类路径上查找对应的类文件。对于数据库驱动,这可能涉及到在多个目录和JAR文件中搜索。
    性能开销: 在寻找适当的数据库驱动时,每一级类加载器都会进行类路径搜索,这可能导致一些性能开销。即使在最终找到驱动后,这种搜索过程仍然可能占用不少时间。

性能优化解决方案:

  • 为了缓解数据库驱动加载可能带来的性能开销,开发者可以采取以下优化策略:
    驱动管理器: 使用驱动管理器(如Java的DriverManager)可以避免频繁地加载和卸载数据库驱动,从而减少了在加载时的性能开销。
    连接池: 使用连接池可以在应用启动时加载并初始化数据库驱动,之后重复使用连接,从而避免每次请求都加载驱动的开销。
    预加载驱动: 在应用程序启动时,可以显式加载并初始化数据库驱动,以确保驱动在需要时已经准备好,减少实际加载时的延迟,就是提前加到缓存中。
    模块化系统: 在模块化系统中,可以将数据库驱动与应用程序其他部分分离,使其能够在需要时独立加载,减少了全局的类路径搜索。
  • 虽然双亲委派模型的类加载机制确保了类的一致性和安全性,但在一些性能敏感的情况下,可能需要采取适当的措施来减轻类加载带来的性能折扣。

结论

  • 双亲委派模型在许多场景下表现优秀,是很重要的规范其确保了类加载的安全性和一致性,但在有些的灵活性需求下可能会限制类加载的自由度。历史上jdk内部也有自己打破双亲委派的例子,为了这些问题,我们可以采取自定义加载器、模块化框架等方式,以实现更为灵活和适应性强的类加载方式,从而满足不同场景下的需求。
相关推荐
ZL不懂前端23 分钟前
Content Security Policy (CSP)
前端·javascript·面试
杜杜的man33 分钟前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*33 分钟前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu35 分钟前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s36 分钟前
Golang--协程和管道
开发语言·后端·golang
为什么这亚子38 分钟前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
想进大厂的小王1 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
测试界萧萧1 小时前
外包干了4年,技术退步太明显了。。。。。
自动化测试·软件测试·功能测试·程序人生·面试·职场和发展
百事老饼干1 小时前
Java[面试题]-真实面试
java·开发语言·面试
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea