简体   繁体   English

Java classLoader困境与锁定的罐子

[英]Java classLoader dilemma with locked jars

I was playing around with classLoaders in Java and noticed a strange thing. 我正在玩Java中的classLoaders并注意到一件奇怪的事情。 If a classLoader loads a class from a jar, this jar is locked indefinitely even if you unreference your classLoader. 如果classLoader从jar加载一个类,即使你没有引用你的classLoader,这个jar也会无限期地被锁定。

In the below example, the jar contains a class called HelloWorld. 在下面的示例中,jar包含一个名为HelloWorld的类。 What I do is try to load the class contained in the jar via a classLoader which adds the jar dynamically. 我所做的是尝试通过classLoader加载jar中包含的类,该类动态地添加jar。 If you set skip to true and do not call Class.forName , you can delete the jar but if you do not skip and even if you unreference the classLoader ( classLoader = null ), the jar cannot be deleted until the JVM exits. 如果将skip设置为true并且不调用Class.forName ,则可以删除jar,但如果不跳过,即使不引用classLoaderclassLoader = null ),也不能删除jar,直到JVM退出。

Why is that? 这是为什么?

PS: I am using java 6 and the code is very verbose for testing purposes 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();
    }

}

I have found an answer and a workaround. 我找到了答案和解决方法。

Based on this article and this amazing related article , it is a bad habit to use Class.forName(className, true, classLoader) because it keeps the class cached in the memory indefinitely. 基于这篇文章和这篇惊人的相关文章 ,使用Class.forName(className, true, classLoader)是一个坏习惯Class.forName(className, true, classLoader)因为它使类无限期地缓存在内存中。

The solution was to use classLoader.loadClass(clasName) instead, then once finished, unreference the classLoader and call the garbage collector using: 解决方案是使用classLoader.loadClass(clasName) ,然后完成后, classLoader.loadClass(clasName)引用classLoader并使用以下命令调用垃圾收集器:

classLoader = null;
System.gc();

Hope this helps others! 希望这有助于他人! :) :)

Background Info: 背景资料:

My project was a complexe one: we had a GWT server acting as a RMI client to another server. 我的项目很复杂:我们有一台GWT服务器充当另一台服务器的RMI客户端。 So to create instances, GWT needed to download the classes from the server and load them. 因此,要创建实例,GWT需要从服务器下载类并加载它们。 Later, GWT would resend instance to the server to persist them in database using Hibernate. 稍后,GWT会将实例重新发送到服务器,以使用Hibernate将它们保存在数据库中。 In order to support hot deployment, we opted for dynamically class loading where a user would upload a jar and notifies the server who would load the classes from it and present them as available to GWT server 为了支持热部署,我们选择动态类加载,用户上传jar并通知服务器谁将从中加载类并将它们呈现给GWT服务器

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM