Java中如何卸载类?

196

我有一个自定义类加载器,这样桌面应用程序就可以动态地从我需要访问的应用服务器中开始加载类。我们这样做是因为要加载的jar包数量太多了(如果我们想要发布它们的话)。如果不在运行时从AppServer库动态加载类,我们还会遇到版本问题。

现在,我遇到了一个问题,我需要与两个不同的AppServer通信,并发现根据我先加载谁的类可能会出现严重故障... 有没有办法在不实际杀死JVM的情况下强制卸载类?

希望这能说得清楚。


您是否为每个jar文件都有一个类加载器?OSGI容器如何归档以卸载类加载器?看起来在类加载器类中没有卸载API? - hetaoblog
7个回答

207
唯一卸载类的方法是使用的类加载器被垃圾回收。这意味着,对于每个类和类加载器本身的引用都需要消失。
你的问题的一个可能解决方案是为每个JAR文件创建一个类加载器,并为每个AppServer创建一个类加载器,将类的实际加载委托给特定的JAR类加载器。这样,您可以为每个应用服务器指向不同版本的JAR文件。
然而,这并不容易。OSGi平台正努力实现这一点,因为每个bundle都有一个不同的类加载器,并且依赖项由平台解析。也许一个好的解决方案是看看它。
如果您不想使用OSGI,一种可能的实现方式是为每个JAR文件使用JarClassloader类的一个实例。
并创建一个新的MultiClassloader类来扩展Classloader。这个类内部会有一个JarClassloaders的数组(或列表),在defineClass()方法中,它会遍历所有内部类加载器,直到找到定义,或抛出NoClassDefFoundException。可以提供一些存取器方法来添加新的JarClassloaders到该类中。网络上有几种可能的MultiClassLoader实现,所以您甚至可能不需要编写自己的实现。
如果为每个连接到服务器实例化一个MultiClassloader,在原则上每个服务器使用相同类的不同版本是可能的。

我在一个项目中使用了MultiClassloader的思想,其中包含用户定义脚本的类必须从内存中加载和卸载,并且它运行得非常好。


35
请注意,根据http://java.sun.com/docs/books/jls/second_edition/html/execution.doc.html 的说明,类的卸载是一种优化方式,具体是否发生取决于JVM实现。 - user21037
5
作为 OSGi 的更简单、轻量级的替代方案,可以尝试使用 JBoss Modules(模块化类加载器,每个模块有一个类加载器来加载一组 JAR 包)。 - Ondra Žižka

49

是的,有方法可以加载类并稍后“卸载”它们。关键是实现自己的类加载器,该加载器位于高级类加载器(系统类加载器)和应用服务器的类加载器之间,并希望应用服务器的类加载器将类加载委托给上层加载器。

一个类由其包,名称和最初加载它的类加载器定义。编写一个“代理”类加载器,它是启动JVM时加载的第一个类加载器。工作流程:

  • 程序启动,真正的“main”类由此代理类加载器加载。
  • 然后通常加载每个类(即不通过其他可能破坏层次结构的类加载器实现)都将被委派给此类加载器。
  • 代理类加载器将java.xsun.x委派给系统类加载器(这些必须不通过任何其他类加载器加载,而只能通过系统类加载器加载)。
  • 对于每个可替换的类,实例化一个类加载器(该类加载器实际加载类而不是将其委派给父类加载器)并通过该类加载器加载它。
  • 在数据结构中(例如Hashmap)将类的包/名称存储为键,类加载器作为值。
  • 每次代理类加载器收到以前加载过的类的请求时,它都会从之前存储的类加载器返回该类。
  • 定位类的字节数组应该足够使您的类加载器(或从数据结构中“删除”键/值对)和重新加载类,以防需要更改它。

如果正确执行,就不应出现ClassCastExceptionLinkageError等问题。

如需了解类加载器层次结构的更多信息(是的,这正是您在此实现的内容;-),请参阅Ted Neward的《服务器端Java编程》——这本书帮助我实现了与您想要的非常相似的东西。


4
我不明白那些在未留下评论的情况下对这个答案打了-1的人。对我来说,这个答案很好。也许每个类都有一个ClassLoader有点太多了,每个JAR文件一个ClassLoader是有道理的。可以更具体地说明如何在提议的方案中强制上传类吗?例如,我怎样才能保证被ClassLoaderA加载的类的实例不被ClassLoaderB加载的实例所引用? - dma_k
1
@dma_k 没错,答案不错,但它没有涉及到你提到的关键点。 - zinking
@Georgi 有没有现成的实现代码可以我们可以实例化/重用来完成这个任务? - Sled
1
如果您能提供Java示例代码,那将非常有帮助。更确切地说,我正在寻找如何使用CustomClassLoader卸载类的方法,但是一直没有成功。 - Sriharsha g.r.v
@Sriharshag.r.v 你尝试过这个并实现了一个样例吗? - niaomingjian

18

我编写了一个自定义的类加载器,可以在不使用GC类加载器的情况下卸载单个类。 Jar Class Loader


运行得非常好 :). 有没有一种方法可以卸载jar文件中的所有类文件? - Ercksen
很遗憾目前还不支持,但我们会研究一下。也许在未来的版本中会加入。 - Kamran
顺便说一下,我找到了一个小的解决方法。如果你为每个已加载的jar文件都有一个JarClassLoader,你可以在其上调用getLoadedClasses(),然后迭代每个类并卸载它。 - Ercksen

13

类加载器可能是一个棘手的问题。如果您使用多个类加载器并且没有明确定义它们之间的交互,那么您可能会遇到问题。我认为,为了真正能够卸载一个类,您需要删除所有对任何类(及其实例)的引用,这样才能实现。

大多数需要执行此类操作的人最终使用 OSGi。OSGi非常强大,而且惊人地轻量级且易于使用。


7

你可以卸载一个ClassLoader,但你不能卸载特定的类。更具体地说,你不能卸载在不受你控制的ClassLoader中创建的类。

如果可能的话,建议使用自己的ClassLoader以便卸载。


4

类与其ClassLoader实例之间存在隐式强引用,反之亦然。它们像Java对象一样进行垃圾回收。如果不使用工具接口或类似方法,则无法删除单个类。

仍然可能出现内存泄漏。对您的类或类加载器的任何强引用都将导致整个内容泄漏。例如,在Sun的ThreadLocal、java.sql.DriverManager和java.beans实现中就会发生这种情况。


-3
如果你正在实时观察 JConsole 或其他类卸载情况,尝试在类卸载逻辑的末尾添加 java.lang.System.gc()。它会显式地触发垃圾回收器。

4
注意:System.gc() 不一定会调用垃圾回收(GC)。它只是向 JVM 发出启动垃圾回收的请求,但不强制执行。在我的经验中,它通常不会启动 GC :-\ - Juh_

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接