摘要:本文次要介绍类加载器、自定义类加载器及类的加载和卸载等内容,并举例介绍了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 。

点击关注,第一工夫理解华为云陈腐技术~