7

Java で classLoaders をいじっていて、奇妙なことに気付きました。classLoader が jar からクラスをロードする場合、classLoader の参照を解除しても、この jar は無期限にロックされます。

以下の例では、jar に HelloWorld というクラスが含まれています。私がしていることは、jar を動的に追加する classLoader を介して、jar に含まれるクラスをロードしようとすることです。に設定skipしてtrue呼び出さないClass.forName場合は jar を削除できますが、スキップせずにclassLoader( classLoader = null) を参照解除しても、JVM が終了するまで jar を削除できません。

何故ですか?

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();
    }

}
4

2 に答える 2

10

答えと回避策を見つけました。

この記事とこの驚くべき関連記事Class.forName(className, true, classLoader)に基づくと、クラスを無期限にメモリにキャッシュしておくため、使用するのは悪い習慣です。

classLoader.loadClass(clasName)解決策は、代わりに使用することでした。終了したら、参照を解除し、次classLoaderを使用してガベージ コレクターを呼び出します。

classLoader = null;
System.gc();

これが他の人に役立つことを願っています! :)

背景情報:

私のプロジェクトは複雑なものでした。別のサーバーへの RMI クライアントとして機能する GWT サーバーがありました。そのため、インスタンスを作成するために、GWT はサーバーからクラスをダウンロードしてロードする必要がありました。その後、GWT はインスタンスをサーバーに再送信し、Hibernate を使用してデータベースに永続化します。ホット デプロイメントをサポートするために、ユーザーが jar をアップロードし、そこからクラスをロードして GWT サーバーに利用可能として提示するサーバーに通知する、動的なクラス ロードを選択しました。

于 2012-06-30T10:37:11.770 に答える
3

Java 7のURLClassLoaderには、#close()これを修正するメソッドがあります。

于 2012-06-30T10:21:14.040 に答える