如何“关闭”一个ClassLoader?

11

我有一个需求,需要在我的应用程序中创建大量的类加载器,以便在用户提供的脚本运行时暂时使一些代码可见。我使用URLClassLoader实现了这个功能,并且效果很好。

当脚本终止时,我想要“卸载”或“关闭”类加载器以释放资源。

将类加载器的引用设置为null是否足够?我特别担心因为额外的类在JAR文件中而最终耗尽文件句柄。

注:必须能够在Java 5及以上版本中使用。是的,我知道...


这个链接可能有帮助:https://dev59.com/InVC5IYBdhLWcg3w51ry? - Thomas
你想要移除对象引用或卸载一个类。因此,你担心tenured或permgen。 - ssedano
实际上,我担心开放连接(即打开的InputStreams以加载资源)。 - Aaron Digulla
6个回答

9
有点晚了,但希望这对那些稍后来到这个问题的人(像我一样)有所帮助。 在Java 7中,URLClassLoader添加了close()方法,这正是OP所要求的。 编辑(感谢@Hot Licks):好吧,它并不完全符合OP的要求。它不能释放所有资源,也不能使资源和加载器可收集。它只是防止使用类加载器加载更多资源。然而,它确实关闭了使用URLClassLoader加载的jar文件。

close() 方法并不会将加载器及其资源变为可回收,它仅仅阻止了更多类的加载。 - Hot Licks
谢谢。不确定我是怎么错过了那个。然而,它似乎关闭了JAR文件,这意味着如果我理解正确的话,现在可以删除/修改JAR文件了? - aspiring_sarge
是的,它意味着jar文件已经关闭。 - Hot Licks

5
如果您不能使用Java7及其close()方法,可以使用反射来关闭类加载器中所有打开的JAR归档文件,具体操作如下:
public void close() {
try {
   Class clazz = java.net.URLClassLoader.class;
   java.lang.reflect.Field ucp = clazz.getDeclaredField("ucp");
   ucp.setAccessible(true);
   Object sun_misc_URLClassPath = ucp.get(this);
   java.lang.reflect.Field loaders = 
      sun_misc_URLClassPath.getClass().getDeclaredField("loaders");
   loaders.setAccessible(true);
   Object java_util_Collection = loaders.get(sun_misc_URLClassPath);
   for (Object sun_misc_URLClassPath_JarLoader :
        ((java.util.Collection) java_util_Collection).toArray()) {
      try {
         java.lang.reflect.Field loader = 
            sun_misc_URLClassPath_JarLoader.getClass().getDeclaredField("jar");
         loader.setAccessible(true);
         Object java_util_jar_JarFile = 
            loader.get(sun_misc_URLClassPath_JarLoader);
         ((java.util.jar.JarFile) java_util_jar_JarFile).close();
      } catch (Throwable t) {
         // if we got this far, this is probably not a JAR loader so skip it
      }
   }
} catch (Throwable t) {
   // probably not a SUN VM
}
return;
}

我正在使用Java 1.6,我强制进行了完整的GC以清理URLClassloader,但我仍然看到lsof显示的许多JAR文件的FD。您的代码解决了我的问题! - waltersu

4
当类加载器加载的所有类都不再有任何引用,并且所有对类加载器本身的引用都被清除时,类加载器及其所加载的类将作为一个整体进行垃圾回收。
请注意,这取决于JVM属性的设置,该属性会导致未引用的类被卸载。在大多数环境中,默认情况下会设置此属性,但在某些嵌入式情况下可能不会设置。
[请注意,删除对类的引用并不是一件简单的事情。任何其他按名称引用它的类都当然会阻止其删除。因此,必须使用ClassLoader.findClass或类似的方法来加载该类。]

不是的。这是比较晦涩的一个,可能在后期的JVM上没有外部化。 - Hot Licks
这是硬币的一面。如果我更改包含类的 JAR 文件,创建一个新的类加载器并通过完全相同的 URL 加载,我是否会获得新的类,还是存在缓存? - Aaron Digulla
据我所知,每个类加载器都有自己的解析路径,因此您可以在两个不同的类加载器中搜索相同的类并获得两个不同的类。某些类(例如那些具有本机方法的类)只能由一个类加载器加载一次,但是对“普通”类不适用此限制。(然而,这整个领域是太阳公司过去进行了多次更改的领域,我的知识已经过时3年。) - Hot Licks
确实它们是不同的类,即使它们共享相同的名称。来自类加载器A的MyClass类型对象甚至无法转换为来自类加载器B的MyClass类型。这是Groovy易于使用的原因之一:动态变量类型和方法调用可以在不考虑对象真实类的情况下工作。 - Corrodias

3
如果您不再从该类加载器中加载类和对象,且不保留对该类加载器的任何引用,则该类加载器将自动由垃圾收集器处理。

1

URL类加载器或其任何父类中都没有close()方法,所以你很遗憾。

难道不应该由GC处理吗?


同意,只需停止引用您的ClassLoader(特别是从其父级)它就会被收集,其类也将随之。 - Guillaume

-1

我扩展了URLClassLoader并基于Java 7创建了一个close方法。我想在我的iPad 2上开发我的IRC机器人,所以我做了必要的事情。现在我的插件系统在Java 6和7上都很稳定,太棒了。


4
你的 close() 方法做什么? - Aaron Digulla
(我敢打赌它不会使加载器可回收。) - Hot Licks

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