クラスローダメモその1

クラスローダとはクラスをメモリ上にロードするもの。

J2SEでは以下の3つがある。自作することも可能。

  • ブートストラップクラスローダ:rt.jarのクラスローダ
  • 拡張クラスローダ:lib/extのクラスローダ
  • システムクラスローダ:classpathのクラスローダ

クラスローダには親子関係があり、子は親を知っているが、親は子を知らない。兄弟も知らない。

子クラスローダに、クラスのロードの依頼があると、親に委譲し、最初に発見されたクラスがロードされる(parent first)。

具体的にjava.lang.ClassLoaderのloadClassメソッドを見ると、
findLoadedClassですでにロードされていないか調べて、もしロードされていないなら、親に委譲(ブートストラップクラスローダまで)。
見つからなかったら、findClassで探す。となっている。

    protected synchronized Class<?> loadClass(String name, boolean resolve)
	throws ClassNotFoundException
    {
	// First, check if the class has already been loaded
	Class c = findLoadedClass(name);
	if (c == null) {
	    try {
		if (parent != null) {
		    c = parent.loadClass(name, false);
		} else {
		    c = findBootstrapClass0(name);
		}
	    } catch (ClassNotFoundException e) {
	        // If still not found, then invoke findClass in order
	        // to find the class.
	        c = findClass(name);
	    }
	}
	if (resolve) {
	    resolveClass(c);
	}
	return c;
    }

実際に、以下のサンプルを試すと、

public class Main {

	public static void main(String[] args) {
		// Bootstrap class loader
		System.out.println("●ブートストラップ・クラスローダ");
		ClassLoader bootstrap = String.class.getClassLoader();
		System.out.println(bootstrap);
		if (bootstrap != null) {
			ClassLoader parent = bootstrap.getParent();
			System.out.println(bootstrap + "の親は" + parent);
		} else {
			System.out.println("親なし");
		}
		// 拡張クラスローダ
		System.out.println("●拡張クラスローダ");
		ClassLoader extensions = sun.net.spi.nameservice.dns.DNSNameService.class
				.getClassLoader();
		System.out.println(extensions);
		if (extensions != null) {
			ClassLoader parent = extensions.getParent();
			System.out.println(extensions + "の親は" + parent);
		} else {
			System.out.println("親なし");
		}
		// システム・クラスローダ
		System.out.println("●システム・クラスローダ");
		ClassLoader systems = DemoClass.class.getClassLoader();
		System.out.println(systems);
		if (systems != null) {
			ClassLoader parent = systems.getParent();
			System.out.println(systems + "の親は" + parent);
		} else {
			System.out.println("親なし");
		}
		// コンテキスト・クラスローダ
		System.out.println("●コンテキスト・クラスローダ");
		ClassLoader threads = Thread.currentThread().getContextClassLoader();
		System.out.println(threads);
		if (threads != null) {
			ClassLoader parent = threads.getParent();
			System.out.println(threads + "の親は" + parent);
		} else {
			System.out.println("親なし");
		}
	}
}

//テスト用クラス
class DemoClass {
	String getClassName() {
		return "DemoClass";
	}
}

以下のように出力される。ここでコンテキスト・クラスローダとは、そのスレッド内でクラスをロードする場合に使用されるクラスローダのことです。

●ブートストラップ・クラスローダ
null
親なし
●拡張クラスローダ
sun.misc.Launcher$ExtClassLoader@35ce36
sun.misc.Launcher$ExtClassLoader@35ce36の親はnull
●システム・クラスローダ
sun.misc.Launcher$AppClassLoader@11b86e7
sun.misc.Launcher$AppClassLoader@11b86e7の親はsun.misc.Launcher$ExtClassLoader@35ce36
●コンテキスト・クラスローダ
sun.misc.Launcher$AppClassLoader@11b86e7
sun.misc.Launcher$AppClassLoader@11b86e7の親はsun.misc.Launcher$ExtClassLoader@35ce36

一方、Tomcatでは親に委譲する前に自分で探している(parent last。delegateフィールドはデフォルトfalseっぽい)。

WebappClassLoader

        boolean delegateLoad = delegate || filter(name);

        // (1) Delegate to our parent if requested
        if (delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader1 " + parent);
            ClassLoader loader = parent;
            if (loader == null)
                loader = system;
            try {
                clazz = loader.loadClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                ;
            }
        }

        // (2) Search local repositories
        if (log.isDebugEnabled())
            log.debug("  Searching local repositories");
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Loading class from local repository");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
        } catch (ClassNotFoundException e) {
            ;
        }

        // (3) Delegate to parent unconditionally
        if (!delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader at end: " + parent);
            ClassLoader loader = parent;
            if (loader == null)
                loader = system;
            try {
                clazz = loader.loadClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                ;
            }
        }

参考: