[JVM]およびインスタンスを解決するための親クラスローダのデリゲートメカニズム

クラスローダプラスリビング例

まず、クラスローダと両親デリゲートメカニズムを導入します

JVMでは、クラスは、仮想マシンプロセスにロードされた三つのステップ、すなわちローディング、接続および初期化を含みます。クラスローダによって、このプロセスのロードが、されてClassLoaderロードされ、クラスローダは、この義務のために、本質的に責任があります。

ジャワ自体はクラスローダ、ブートクラスローダのいくつかの種類を提供してくれBootstrap ClassLoader、拡張クラスローダーExtension ClassLoaderは、アプリケーションクラスローダApp ClassLoader上記の3種類に加えて、我々は我々自身のクラスローダを定義することができ、その後、私達はちょうどに必要継承したClassLoaderクラスを、そのオーバーライドするfindClass()方法をすることができます。

もちろん、時には、あなたは私たちがクラスパスにロードするカスタムクラスローダを使用する場合とは思いませ考えるClassPathにA classファイルを、次に呼び出すClassオブジェクトをgetClassLoader()メソッドは、私たちはクラスローダがクラスをロードすることを見つけたとき私たちのカスタムローダーは理由として、ではないのですか?これは後述します。図は、JVMのクラスローダのメカニズムであります

  • 1、ブートクラスローダBootstrap ClassLoader

    スタートクラスローダが担当する%JRE_HOME/lib/たとえば、ディレクトリ内の関連するクラスファイルrt.jartools.jarおよびように、例えば、私たちのStringクラスは中に格納されrt.jar、ローダがありますBootstrap ClassLoader

  • 2、拡張クラスローダーExtension ClassLoader

    拡張クラスローダは責任がある%JRE_HOME/lib/extたとえば、ディレクトリ内の関連するクラスファイルrt.jartools.jarなど

  • 3、アプリケーションクラスローダApp ClassLoader

    アプリケーションのクラスパスにロードを担当するアプリケーションクラスローダclassファイル。

:それはクラスパスの問題に来るとき、私たちは、クラスパスのjavaは、我々が構成されたシステム環境変数の値を参照することを説明するCLASSPATHが、この値に限定されません。最初に私たちのローカルプリントの小さなシリーズclasspathの値:

.;D:\development\jdk\lib\dt.jar;D:\development\jdk\lib\tools.jar;

あなたは、これは単に私たちはカタログに使用するツールはありませんが、その後がわかりますJVM、それは我々がどのようにロードされeclipse、それが記述されたクラス?答えはということであるeclipse私たちに何とかすべてを支援してきました。

我々は例を与えるの下には、見てStringいるクラスローダの種類:

System.out.println(String.class.getClassLoader());

上記文章コード出力nullなぜ?クラスがあることがある場合ためBootstrap ClassLoader、またはExtension ClassLoader負荷、getClassLoader()所定の出力nullため、そしてStringクラスが存在しrt.jar、それがされますBootstrap ClassLoader読み込まれ、その出力null

クラスローダに目を向けると、私たちは両親のデリゲートメカニズムを述べ、そのクラスの親デリゲートメカニズムを言わなければならない、小扁は、一つの文章にまとめました:

カスタムローダーローダーは、親のP P1を持っている場合、親ローダP1 P2が存在する場合、最上位の更なるブートストラップクラスローダ場合、それは、P2に割り当てられたタスクをロードする、父親のP1にデリゲートタスクをロードする前にロードされますクラスがロードされるまでロードされた順序に対する負荷少なく、再び~~

第二に、ClassLoaderクラスの機能を導入します

  • 1.各Classインスタンスが含まClassLoader参照

    したがって、我々は常に可能ClassオブジェクトgetClassLoader()のクラスローダを取得する方法を。もちろん、上記のようにクラスがあることがある場合、前記Bootstrap ClassLoader、またはExtension ClassLoaderローディング、getClassLoader()所定の出力nullこれは、私たちが注意を払う必要がポイントです。

  • 2は、配列型のための[]オブジェクト、彼らはクラスローダではありませんClassLoaderロードされるが、必要に応じて、Java仮想マシンが自動的に作成します。アレイのタイプを介してClassオブジェクトgetClassLoader()クラスローダ内で使用される素子のアレイを持つメソッドの戻り値として、しかし配列要素タイプは、元である場合intハン、getClassLoader()メソッドが戻りますnull

    // sun.misc.Launcher$AppClassLoader@73d16e93
    ClassLoaderTest[] array = new ClassLoaderTest[5];
    System.out.println(array.getClass().getClassLoader());
    // null
    int[] intArray = new int[3];
    System.out.println(intArray.getClass().getClassLoader());
    复制代码
  • 3、我々は継承できClassLoader、独自のクラスローダを実装するクラスを

/**
 * 自定义类加载器
 * @Author jiawei huang
 * @Since 2019年8月7日
 * @Version 1.0
 */
public class MyClassLoader extends ClassLoader {
    // 如果我们从其他地方进行加载,我们可以指定路径
    private String classpath;
    public MyClassLoader(String classPath) {
    	classpath = classPath;
    }
    public MyClassLoader() {
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    	Class clazz = null;
    	// 获取该class文件字节码数组
    	byte[] classData = getClassData();
    	if (classData != null) {
    		// 将class的字节码数组转换成Class类的实例
    		clazz = defineClass(name, classData, 0, classData.length);
    	}
    	return clazz;
    }
    
    private byte[] getClassData() {
    	byte[] bytes = null;
    	File file = new File(classpath);
    	if (file.exists()) {
    		// 从文件中读取class字节数据
    		FileInputStream in = null;
    		ByteArrayOutputStream out = null;
    		try {
    			in = new FileInputStream(file);
    			out = new ByteArrayOutputStream();
    
    			byte[] buffer = new byte[1024];
    			int size = 0;
    			while ((size = in.read(buffer)) != -1) {
    				out.write(buffer, 0, size);
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				in.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    		bytes = out.toByteArray();
    	}
    	return bytes;
    }
}
复制代码

我々は仮定exパスの下のパッケージを持ってMain.java、我々は次のような出力を見てみましょう:

A.java
MyClassLoader loader = new MyClassLoader();
Class<?> clazzClass = loader.loadClass("ex.Main");
// 1
System.out.println(clazzClass.getClassLoader());
复制代码

上記の1出力でsun.misc.Launcher$AppClassLoader@73d16e93、我々は明らかに私がロードするために自分自身のクラスローダを使用していた、頼むかもしれないex.Main理由の出力だった、うんAppClassLoaderクラスローダは、それは?実際には、これは親委譲メカニズムは、あるMyClassLoaderタスクがその親ローダにロードされているApp ClassLoaderだけで、ex.Mainクラスパスに回すので、App ClassLoader直接返さロードした後。

  • 4、ライフサイクル全体クラスは一度だけロードされ、そして一度だけでしょう

  • 図5は、並列ロードローダーのサポートは、並列ローダーと呼ばれるが、私たちは、カスタムローダの初期化を持っている場合にのみ、呼び出しClassLoader.registerAsParallelCapable();、どのようにそれを行うために自分自身を登録する方法を?私たちは見とりURLClassLoader、ソースコードを。

static {
    // 注册自己
    ClassLoader.registerAsParallelCapable();
}
复制代码
  • 6は、委員会のモデルの場合には厳密に階層的ではない、カスタムクラスローダは、平行する能力を持っている必要がある、またはローダーロックは、クラスのロード処理で開催されているため、クラスローディングは、デッドロックにつながることができます。

私たちは、詳細を確認ClassLoaderするloadClass()方法出典:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    // 这里有个锁
    synchronized (getClassLoadingLock(name)) {
        1、先检查这个名称的类是否已经被加载过,如果是,就不再加载了,这也印证了我们第4点说的一个类只会被加载一次
        Class<?> c = findLoadedClass(name);
        // 2、如果该类没有被加载,那么就进行加载
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 3、如果存在父加载器,就进行委派,这就是双亲委派机制的原理
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 4、去`Bootstrap classloader`加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            // 5、如果3,4都没有加载到,那就执行我们自定义的classloader,这也是为什么我们要重写findClass方法的原因所在
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);
    
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
复制代码
  • 7、我々だけでなく、ファイルシステムからロードされたクラスに、我々はまた、ネットワークから得ることができることができますclassアクセス、ロードファイルbyte[]、およびその後によるだろうに変換するオブジェクト、そして最終的に通じ~~~ Javaオブジェクトにそれを回すためにClassLoaderdefineClass()byte[]ClassClass.newInstance()
ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();
复制代码

第三に、SpringBootコンストラクタの中で、なぜ追加しClassLoaderたパラメータを?

初期化プロセスなので、load()この方法は、異なる場所からクラスファイルをロードする必要があります。

protected void load(ApplicationContext context, Object[] sources) {
    if (logger.isDebugEnabled()) {
    	logger.debug(
    			"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    }
    BeanDefinitionLoader loader = createBeanDefinitionLoader(
    		getBeanDefinitionRegistry(context), sources);
    if (this.beanNameGenerator != null) {
    	loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
    	loader.setResourceLoader(this.resourceLoader);
    }
    if (this.environment != null) {
    	loader.setEnvironment(this.environment);
    }
    loader.load();
}
复制代码

おすすめ

転載: juejin.im/post/5d4a89a36fb9a06ae61aa1c8