卸载Java中的类?


174

我有一个自定义的类加载器,以便桌面应用程序可以动态地从需要与之交谈的AppServer加载类。我们这样做是因为这样做所需的罐子数量太可笑了(如果我们想发货的话)。如果我们没有在运行时从AppServer库动态加载类,我们还将遇到版本问题。

现在,我遇到一个问题,需要与两个不同的AppServer进行交谈,发现根据我首先加载的类,我可能会严重损坏...是否有任何方法可以在不杀死JVM的情况下强制卸载该类?

希望这有意义


每个罐子都有类加载器吗?OSGI容器如何归档以卸载类加载器?看来classloader类中没有卸载API?
hetaoblog 2011年

Answers:


190

可以卸载类的唯一方法是,如果使用的类加载器是垃圾回收。这意味着,对每个单个类的引用以及对类加载器的引用都需要遵循dodo的方式。

一种可能的解决方案是为每个jar文件提供一个Classloader,为每个AppServer提供一个Classloader,以将类的实际加载委托给特定的Jar类加载器。这样,您可以为每个App服务器指向jar文件的不同版本。

但是,这并非微不足道。OSGi平台正努力做到这一点,因为每个捆绑软件具有不同的类加载器,并且依赖项由平台解决。也许一个好的解决方案就是看看它。

如果您不想使用OSGI,则一种可能的实现方式是为每个JAR文件使用一个JarClassloader类实例。

并创建一个扩展Classloader的新的MultiClassloader类。此类在内部将具有JarClassloaders的数组(或List),并且在defineClass()方法中将迭代所有内部类加载器,直到找到定义或抛出NoClassDefFoundException为止。可以提供几个访问器方法,以将新的JarClassloaders添加到该类。网上有多种针对MultiClassLoader的实现,因此您甚至不需要编写自己的实现。

如果为服务器的每个连接实例化一个MultiClassloader,则原则上每个服务器都可能使用同一类的不同版本。

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


31
还要注意,根据java.sun.com/docs/books/jls/second_edition/html/…,类的卸载是一种优化,并且取决于JVM的实现,实际上可能会或可能不会发生。

5
作为OSGi的一种更轻松,更轻量的替代方法,请尝试使用JBoss模块 -模块化的类加载,每个模块(一组jar)都带有类加载器。
OndraŽižka2013年

42

是的,有一些方法可以加载类并在以后“卸载”。诀窍是实现自己的类加载器,该类加载器位于高层类加载器(系统类加载器)与应用服务器的类加载器之间,并希望应用服务器的类加载器确实将类加载委托给上层加载器。

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

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

完成后不应出现ClassCastExceptionLinkageError等。

有关类加载器层次结构的更多信息(是的,这正是您在此处实现的内容;-)请参阅Ted Neward撰写的“基于服务器的Java编程” -该书帮助我实现了与您想要的东西非常相似的东西。


3
我不明白,在不作任何评论的情况下在答案中打-1的人。对我来说看起来不错。也许ClassLoader每个班级只有一个太多,ClassLoader每个JAR 一个都有意义。可以更具体地说明如何在建议的模式中强制类上传吗?例如,我如何保证ClassLoaderB加载的实例不会引用ClassLoaderA加载的类的实例?
dma_k 2011年

@dma_k完全正确,答案很好,但是并没有触及您提到的关键点。
zinking 2013年

@Georgi是否存在可以实例化/重复使用的现有实现?
雪橇

1
如果您可以提供示例Java代码,那将非常有帮助。确切地说,我正在寻找如何使用CustomClassLoader卸载类,但是没有运气。
Sriharsha grv

@ Sriharshag.rv您是否尝试过并实现了示例?
niaomingjian

17

我编写了一个自定义的类加载器,可以从中卸载单个类而无需通过GC加载类加载器。罐类装载机


奇迹般有效 :)。有什么方法可以卸载jar文件中的所有类文件?
Ercksen

不幸的是目前还没有。但是会调查一下。可能在将来的版本中。
卡姆兰2015年

顺便说一句,我发现了一些解决方法。如果JarClassLoader每个加载的jar文件都有一个getLoadedClasses(),则可以对其进行调用,然后遍历每个文件并将其卸载。
Ercksen

12

类加载器可能是一个棘手的问题。如果您使用多个类加载器,并且没有清晰,严格地定义它们的交互,则尤其会遇到问题。我认为,为了能够真正卸载您要去的类,必须删除对要卸载的任何类(及其实例)的所有引用。

大多数需要做这种事情的人最终都使用OSGi。OSGi确实非常强大,而且重量轻且易于使用,


7

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

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


4

类具有对ClassLoader实例的隐式强引用,反之亦然。它们像Java对象一样被垃圾收集。如果不点击工具界面或类似界面,就无法删除单个类。

与以往一样,您可能会遇到内存泄漏。任何对您的类或类加载器之一的强引用都会泄漏整个事情。例如,在Sun的ThreadLocal,java.sql.DriverManager和java.beans实现中就会发生这种情况。


-1

如果您正在实时查看卸载类是否在JConsole中起作用,请尝试java.lang.System.gc()在类卸载逻辑的末尾添加 。它显式触发垃圾收集器。


3
注意:System.gc()不必调用GC。它仅要求jvm启动它,但不强制执行它。而且IME通常不会启动GC:-\
Juh_ '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.