最近,遇到了两个和Java类的加载和卸载相关的问题:
-
是一道关于Java的判断题:一个类被首次加载后,会长期留驻JVM,直到JVM退出。这个说法,是不是正确的?
-
在开发的一个集成平台中,需要集成类似接口的多种工具,并且工具可能会有新增,同时在不同的环境部署会有裁剪(例如对外提供服务的应用,不能提供特定的采购的工具),如何才能更好地实现?
针对上面的第2点,我们采用Java插件化开发实现。上面的两个问题,都和Java的类加载和热替换机制有关。
1. Java的类加载器和双亲委派模型
1.1 Java类加载器
类加载器,顾名思义,就是用来实现类的加载操作。每个类加载器都有一个独立的类名称空间,就是说每个由该类加载器加载的类,都在自己的类名称空间,如果要比较两个类是否“相等”,首先这两个类必须在相同的类命名空间,即由相同的类加载器加载(即对于任何一个类,都必须由该类本身和加载它的类加载器一起确定其在JVM中的唯一性),不是同一个类加载器加载的类,不会相等。
在Java中,主要有如下的类加载器:
图1.1 Java类加载器
下面,简单介绍上面这几种类加载器:
启动类加载器(Bootstrap Class Loader):这个类使用C++开发(所有的类加载器中,唯一使用C++开发的类加载器),用来加载/lib目录中jar和tools.jar或者使用 -Xbootclasspath 参数指定的类。
扩展类加载器(Extension Class Loader):定义为misc.Launcher$ExtClassLoader,用来加载/lib/ext目录或者使用java.ext.dir指定的类。
应用程序类加载器(Application Class Loader):定义为misc.Launcher$AppClassLoader,用来加载用户类路径下面(classpath)下面所有的类,一般情况下,该类是应用程序默认的类加载器。
用户自定义类加载器(User Class Loader):用户自定义类加载器,一般没有必要,后面我们会专门来一部分介绍该类型的类加载器。
1.2 双亲委派模型
双亲委派模型,是从 Java1.2 开始引入的一种类加载器模式,在Java中,类的加载操作通过java.lang.ClassLoader中的loadClass()方法完成,咱们首先看看该方法的实现(直接从Java源码中捞出来的):
我们结合上面的注释,来解释下双亲委派模型的内容:
-
接收到一个类加载请求后,首先判断该类是否有加载,如果已经加载,则直接返回;
-
如果尚未加载,首先获取父类加载器,如果可以获取父类加载器,则调用父类的loadClass()方法来加载该类,如果无法获取父类加载器,则调用启动器加载器来加载该类;
-
判断该类是否被父类加载器或者启动类加载器加载,如果已经加载完成则返回,如果未成功加载,则自己尝试来加载该类。
上面的描述,说明了loadClass()方法的实现,我们进一步对上面的步骤进行解释:
因为类加载器首先调父类加载器来进行加载,从loadClass()方法的实现,我们知道父类加载器会尝试调自己的父类加载器,直到启动类加载器,所以,任何一个类的加载,都会最终委托到启动类加载器来首先加载;
在前面有进行介绍,启动类加载器、扩展类加载器、应用程序类加载器,都有自己加载的类的范围,例如启动类加载器只加载JDK核心库,因此并不是父类加载器就可以都加载成功,父类加载器无法加载(一般如上面代码,抛出来ClassNotFoundException),此时会由自己加载。
最后啰嗦一下,再进行一下总结:
双亲委派模型:如果一个类加载器收到类加载请求,会首先把加载请求委派给父类加载器完成,每个层次的类加载器都是这样,最终所有的加载请求都传动到最根的启动类加载器来完成,如果父类加载器无法完成该加载请求(即自己加载的范围内找不到该类),子类加载器才会尝试自己加载。
这样的双亲委派模型有个好处:就是所有的类都尽可能由顶层的类加载器加载,保证了加载的类的唯一性,如果每个类都随机由不同的类加载器加载,则类的实现关系无法保证,对于保证Java程序的稳定运行意义重大。
2. Java的类动态加载和卸载
2.1 Java类的卸载
在Java中,每个类都有相应的Class Loader,同样的,每个实例对象也会有相应的类,当满足如下三个条件时,JVM就会卸载这个类:
-
该类所有实例对象不可达
-
该类的Class对象不可达
-
该类的Class Loader不可达
那么,上面示例对象、Class对象和类的Class Loader直接是什么关系呢?
在类加载器的内部实现中,用一个Java集合来存放所加载类的引用。而一个Class对象总是会引用它的类加载器,调用Class对象的getClassLoader()方法,就能获得它的类加载器。所以,Class实例和加载它的加载器之间为双向引用关系。
一个类的实例总是引用代表这个类的Class对象。在Object类中定义了getClass()方法,这个方法返回代表对象所属类的Class对象的引用。此外,所有的Java类都有一个静态属性class,它引用代表这个类的Class对象。
Java虚拟机自带的类加载器(前面介绍的三种类加载器)在JVM运行过程中,会始终存在,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的。因此,由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。
那么,我们是不是就完全不能在Java程序运行过程中,动态修改我们使用的类了吗?答案是否定的!根据上面的分析,通过Java虚拟机自带的类加载器加载的类无法卸载,我们可以自定义类加载器来加载Java程序,通过自定义类加载器加载的Java类,是可以被卸载的。
2.2 自定义类加载器
前面介绍到,类加载的双亲委派模型,是推荐模型,在loadClass中实现的,并不是必须使用的模型。我们可以通过自定义类加载器,直接加载我们需要的Java类,而不委托给父类加载器。
图2.1 自定义类加载器
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
这份《“java高分面试指南”-25分类227页1000+题50w+字解析》同样可分享给有需要的朋友,感兴趣的伙伴们可挑战一下自我,在不看答案解析的情况,测试测试自己的解题水平,这样也能达到事半功倍的效果!(好东西要大家一起看才香)
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
…(img-2ue1QEpf-1713113578760)]
[外链图片转存中…(img-MUmdLevc-1713113578760)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!