6

コードを動的にロードするために、2 つのカスタム クラス ローダーを作成しました。

最初のものは、Jar からコードをロードします。

package com.customweb.build.bean.include;

import java.net.URL;
import java.net.URLClassLoader;

import com.customweb.build.process.ILeafClassLoader;

public class JarClassLoader extends URLClassLoader implements ILeafClassLoader {

    public JarClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    @Override
    public Class<?> findClassWithoutCycles(String name) throws ClassNotFoundException {

        Class<?> c = findLoadedClass(name);
        if (c != null) {
            return c;
        }

        return findClass(name);
    }

    @Override
    protected Class<?> findClass(String qualifiedClassName) throws ClassNotFoundException {
        synchronized (this.getParent()) {
            synchronized (this) {
                return super.findClass(qualifiedClassName);
            }
        }
    }

    @Override
    public URL findResourceWithoutCycles(String name) {
        return super.findResource(name);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        synchronized (this.getParent()) {
            synchronized (this) {
                return super.loadClass(name);
            }
        }
    }

}

もう一方のクラス・ローダーは、複数のクラス・ローダーを使用して、他のクラス・ローダーのクラスにアクセスできるようにします。最初のインスタンスの初期化中に、このクラス ローダーのインスタンスを親として設定しました。このサイクルを断ち切るために、'findClassWithoutCycles' メソッドを使用します。

package com.customweb.build.process;

import java.net.URL;
import java.security.SecureClassLoader;
import java.util.ArrayList;
import java.util.List;

public class MultiClassLoader extends SecureClassLoader {

    private final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();

    public MultiClassLoader(ClassLoader parent) {
        super(parent);
    }

    public void addClassLoader(ClassLoader loader) {
        this.classLoaders.add(loader);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        for (ClassLoader loader : classLoaders) {
            try {
                if (loader instanceof ILeafClassLoader) {
                    return ((ILeafClassLoader) loader).findClassWithoutCycles(name);
                } else {
                    return loader.loadClass(name);
                }
            } catch (ClassNotFoundException e) {
                // Ignore it, we try the next class loader.
            }
        }

        throw new ClassNotFoundException(name);
    }

    @Override
    protected URL findResource(String name) {

        for (ClassLoader loader : classLoaders) {
            URL url = null;
            if (loader instanceof ILeafClassLoader) {
                url = ((ILeafClassLoader) loader).findResourceWithoutCycles(name);
            } else {
                url = loader.getResource(name);
            }

            if (url != null) {
                return url;
            }
        }

        return null;
    }
}

しかし、このクラスローダーを使用すると、ほとんどの場合デッドロックが発生します。私はここでスレッドダンプを過ぎました: http://pastebin.com/6wZKv4Y0

Java ClassLoader は一部のメソッドで $this で同期することによってスレッドをブロックするため、最初に MultiClassLoader で同期し、次に JarClassLoader で同期しようとします。これにより、ロックを取得する順序が同じ場合にデッドロックが発生するのを防ぐことができます。しかし、ネイティブ クラス ローディング ルーチンのどこかで、クラス ローダーのロックが取得されているようです。スレッド「pool-2-thread-8」がオブジェクト「0x00000007b0f7f710」でロックされているため、この結論に達しました。しかし、ログでは、このロックがいつどのスレッドによって取得されたかを確認できません。

クラスローダーで同期を行っているスレッドを見つけるにはどうすればよいですか?

編集:MultiClassLoaderのloadClassを呼び出す前に、すべてのクラスローダーで同期することで解決しました。

4

1 に答える 1

7

JVM は、loadClass を呼び出す前に ClassLoader のロックを取得します。これは、JarClassLoader の 1 つを介してロードされたクラスが別のクラスを参照し、JVM がその参照を解決しようとした場合に発生します。クラスを作成した ClassLoader に直接移動し、ロックして loadClass を呼び出します。しかし、JarClassLoader を再度ロックする前に、親ローダーをロックしようとしています。したがって、2 つのロックの順序付けは機能しません。

しかし、同期を必要とするリソースにアクセスしていないため、2 つのロックの理由はわかりません。URLClassLoader の継承された内部状態は、その実装自体によって維持されます。

ただし、同期を必要とするクラスにさらに状態を追加する場合は、ClassLoader インスタンスをロックするなどの別のメカニズムを使用する必要があります。

http://docs.oracle.com/javase/7/docs/technotes/guides/lang/cl-mt.html言う

デッドロックのリスクがあるカスタム クラス ローダーがある場合、Java SE 7 リリースでは、次のルールに従ってデッドロックを回避できます。

  1. カスタム クラス ローダーが同時クラス読み込みに対してマルチスレッド セーフであることを確認してください。

    を。内部ロック方式を決定します。たとえば、java.lang.ClassLoader は、要求されたクラス名に基づくロック スキームを使用します。

    b. クラス ローダー オブジェクト ロックだけですべての同期を削除します

    c. 異なるクラスをロードする複数のスレッドに対してクリティカル セクションが安全であることを確認します。

  2. カスタム クラス ローダーの静的イニシャライザで、java.lang.ClassLoader の静的メソッド registerAsParallelCapable() を呼び出します。この登録は、カスタム クラス ローダーのすべてのインスタンスがマルチスレッド セーフであることを示します。

  3. このカスタム クラス ローダーが拡張するすべてのクラス ローダー クラスも、クラス初期化子で registerAsParallelCapable() メソッドを呼び出すことを確認してください。クラスの同時ロードに対してマルチスレッドセーフであることを確認してください。

カスタム クラス ローダーが findClass(String) のみをオーバーライドする場合、それ以上の変更は必要ありません。これは、カスタム クラス ローダーを作成するための推奨メカニズムです。

于 2013-09-05T17:39:33.293 に答える