Java classLoader困境与锁定的jar子

我正在玩Java中的classLoaders并注意到一件奇怪的事情。 如果classLoader从jar加载一个类,即使你没有引用你的classLoader,这个jar也会无限期地被锁定。

在下面的示例中,jar包含一个名为HelloWorld的类。 我所做的是尝试通过classLoader加载jar中包含的类,该类动态地添加jar。 如果将skip设置为true并且不调用Class.forName ,则可以删除jar,但如果不跳过,即使不引用classLoaderclassLoader = null ),也不能删除jar,直到JVM退出。

这是为什么?

PS:我使用的是java 6,代码非常冗长,用于测试目的

 package loader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; public class TestClassLoader { private URLClassLoader classLoader; public TestClassLoader() throws MalformedURLException, IOException { System.out.println("Copying jar"); if (copyJar()) { System.out.println("Copying SUCCESS"); performFirstCheck(); } else { System.out.println("Copying FAILED"); } } public static void main(String[] args) throws IOException { System.out.println("Test started"); TestClassLoader testClassLoader = new TestClassLoader(); System.out.println("Bye!"); } public void performFirstCheck() throws IOException { System.out.println("Checking class HelloWorld does not exist"); if (!checkClassFound(TestClassLoader.class.getClassLoader(), false)) { System.out.println("Deleting jar"); deleteJar(); System.out.println("First Check SUCCESS"); performSecondCheck(); } else { System.out.println("First Check FAILED"); } } private void performSecondCheck() throws IOException { System.out.println("Copying jar"); if (copyJar()) { System.out.println("Copying SUCCESS"); createClassLoaderAndCheck(); } else { System.out.println("Copying FAILED"); } } private void createClassLoaderAndCheck() throws MalformedURLException { System.out.println("Creating classLoader"); createClassLoader(); System.out.println("Checking class HelloWorld exist"); if (checkClassFound(classLoader, true)) { System.out.println("Second Check SUCCESS"); classLoader = null; System.out.println("Deleting jar"); if (deleteJar()) { System.out.println("Deleting SUCCESS"); } else { System.out.println("Deleting FAILED"); } } else { System.out.println("Second Check FAILED"); } } public void createClassLoader() throws MalformedURLException { URL[] urls = new URL[1]; File classFile = new File("C:\\Users\\Adel\\Desktop\\classes.jar"); urls[0] = classFile.toURI().toURL(); classLoader = new URLClassLoader(urls); } public boolean checkClassFound(ClassLoader classLoader, boolean skip) { if (skip) { System.out.println("Skiping class loading"); return true; } else { try { Class.forName("HelloWorld", true, classLoader); return true; } catch (ClassNotFoundException e) { return false; } } } public URLClassLoader getClassLoader() { return classLoader; } public boolean copyJar() throws IOException { File sourceJar = new File("C:\\Users\\Adel\\Desktop\\Folder\\classes.jar"); File destJar = new File("C:\\Users\\Adel\\Desktop\\classes.jar"); if (destJar.exists()) { return false; } else { FileInputStream finput = new FileInputStream(sourceJar); FileOutputStream foutput = new FileOutputStream(destJar); byte[] buf = new byte[1024]; int len; while ((len = finput.read(buf)) > 0) { foutput.write(buf, 0, len); } finput.close(); foutput.close(); return true; } } public boolean deleteJar() { File destJar = new File("C:\\Users\\Adel\\Desktop\\classes.jar"); return destJar.delete(); } } 

我找到了答案和解决方法。

基于这篇文章和这篇惊人的相关文章 ,使用Class.forName(className, true, classLoader)是一个坏习惯Class.forName(className, true, classLoader)因为它使类无限期地缓存在内存中。

解决方案是使用classLoader.loadClass(clasName) ,然后完成后, classLoader.loadClass(clasName)引用classLoader并使用以下命令调用垃圾收集器:

 classLoader = null; System.gc(); 

希望这有助于他人! 🙂

背景资料:

我的项目很复杂:我们有一台GWT服务器充当另一台服务器的RMI客户端。 因此,要创建实例,GWT需要从服务器下载类并加载它们。 稍后,GWT会将实例重新发送到服务器,以使用Hibernate将它们保存在数据库中。 为了支持热部署,我们选择动态类加载,用户上传jar并通知服务器谁将从中加载类并将它们呈现给GWT服务器

在Java 7中, URLClassLoader有一个#close()方法来修复它。