摘要:本文次要介绍类加载器、自定义类加载器及类的加载和卸载等内容,并举例介绍了 Java 类的热替换。
最近,遇到了两个和 Java 类的加载和卸载相干的问题:
1) 是一道对于 Java 的判断题:一个类被首次加载后,会长期留驻 JVM,直到 JVM 退出。这个说法,是不是正确的?
2) 在开发的一个集成平台中,须要集成相似接口的多种工具,并且工具可能会有新增,同时在不同的环境部署会有裁剪(例如对外提供服务的利用,不能提供特定的洽购的工具),如何能力更好地实现?
针对下面的第 2 点,咱们采纳 Java 插件化开发实现。下面的两个问题,都和 Java 的类加载和热替换机制无关。
1. Java 的类加载器和双亲委派模型
1.1 Java类加载器
类加载器,顾名思义,就是用来实现类的加载操作。每个类加载器都有一个独立的类名称空间,就是说每个由该类加载器加载的类,都在本人的类名称空间,如果 要比拟两个类是否 “ 相等”,首先这两个类必须在雷同的类命名空间,即由雷同的类加载器加载(即对于任何一个类,都必须由该类自身和加载它的类加载器一起确定其在 JVM 中的唯一性),不是同一个类加载器加载的类,不会相等。
在 Java 中,次要有如下的类加载器:
图 1.1 Java 类加载器
上面,简略介绍下面这几品种加载器:
- 启动类加载器(Bootstrap Class Loader):这个类应用 C ++ 开发(所有的类加载器中,惟一应用 C ++ 开发的类加载器),用来加载 <JAVA_HOME>/lib 目录中 jar 和 tools.jar 或者应用 -Xbootclasspath 参数指定的类。
- 扩大类加载器(Extension Class Loader):定义为 misc.Launcher$ExtClassLoader,用来加载 <JAVA_HOME>/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 源码中捞进去的):
咱们联合下面的正文,来解释下双亲委派模型的内容:
1) 接管到一个类加载申请后,首先判断该类是否有加载,如果曾经加载,则间接返回;
2) 如果尚未加载,首先获取父类加载器,如果能够获取父类加载器,则调用父类的 loadClass()办法来加载该类,如果无奈获取父类加载器,则调用启动器加载器来加载该类;
3) 判断该类是否被父类加载器或者启动类加载器加载,如果曾经加载实现则返回,如果未胜利加载,则本人尝试来加载该类。
下面的形容,阐明了 loadClass()办法的实现,咱们进一步对下面的步骤进行解释:
- 因为类加载器首先调父类加载器来进行加载,从 loadClass()办法的实现,咱们晓得父类加载器会尝试调本人的父类加载器,直到启动类加载器,所以,任何一个类的加载,都会最终委托到启动类加载器来首先加载;
- 在后面有进行介绍,启动类加载器、扩大类加载器、应用程序类加载器,都有本人加载的类的范畴,例如启动类加载器只加载 JDK 外围库,因而并不是父类加载器就能够都加载胜利,父类加载器无奈加载(个别如下面代码,抛出来 ClassNotFoundException),此时会由本人加载。
最初啰嗦一下,再进行一下总结:
双亲委派模型 : 如果一个类加载器收到类加载申请 , 会首先把加载申请委派给父类加载器实现 , 每个档次的类加载器都是这样 , 最终所有的加载申请都传动到最根的启动类加载器来实现 , 如果父类加载器无奈实现该加载申请 ( 即本人加载的范畴内找不到该类 ), 子类加载器才会尝试本人加载。
这样的双亲委派模型有个益处:就是 所有的类都尽可能由顶层的类加载器加载 , 保障了加载的类的唯一性,如果每个类都随机由不同的类加载器加载,则类的实现关系无奈保障,对于保障 Java 程序的稳固运行意义重大。
2. Java 的类动静加载和卸载
2.1 Java类的卸载
在 Java 中,每个类都有相应的 Class Loader,同样的,每个实例对象也会有相应的类,当满足如下三个条件时,JVM 就会卸载这个类:
1) 该类所有实例对象不可达
2) 该类的 Class 对象不可达
3) 该类的 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 自定义类加载器
如上图所示,咱们有自定义的类加载器 MyClassLoader,用来加载类 MyClass,则在 JVM 中,会存在下面三类援用(上图疏忽这三种类型对象对其余的对象的援用)。如果咱们将右边的三个援用变量,均设置为 null,那么此时,曾经加载的 MyClass 将会被卸载。
2.3 动静卸载存在的问题
动静卸载须要借助于 JVM 的垃圾收集性能才能够做到,然而咱们晓得,JVM 的垃圾回收,只有在堆内存占用比拟高的时候,才会触发。即便咱们调用了 System.gc(),也不会立刻执行垃圾回收操作,而只是通知 JVM 须要执行垃圾回收,至于什么时候垃圾回收,则要看 JVM 本人的垃圾回收策略。
然而咱们不须要乐观,即便动静卸载不是那么可靠,然而实现动静的 Java 类的热替换还是有心愿的。
3. Java 类的热替换
上面通过代码来介绍 Java 类的热替换办法(代码简陋,次要为了阐明问题):
如上面的代码:
首先定义一个自定义类加载器:
下面在 loadClass 时,先判断类 name(蕴含 package 的全限定名)是否以 java 开始,如果是 java 开始,则应用 JVM 自带的类加载器加载。
而后定义一个简略的动静加载类:
在执行过程中,会动静批改打印内容,测试类的热加载。
而后定义一个调用类:
当咱们运行下面 Main 程序过程中,咱们动静批改执行内容(SayHello 中,从 hello zmj… 更改为 hello ping…),最终展现的内容如下:
4. 总结
本文次要介绍类加载器、自定义类加载器及类的加载和卸载等内容,并举例介绍了 Java 类的热替换实现。
其实,最近在开发我的项目中,须要裁剪个性,就想用 pf4j 来做插件化开发,理解了一些类加载机制,整顿一下。
次要参考《深刻 Java 虚拟机:JVM 高级个性与最佳实际》。
本文分享自华为云社区《Java 类动静加载和热替换》,原文作者:maijun。
点击关注,第一工夫理解华为云陈腐技术~