JavaでのClassLoaderの動き(まとめ)

※ソースと出力結果が間違っていたので一部修正(2010/5/11)

プロジェクトでクラスがロードされるのが遅い*1とかで調べることがあったので、実際どんな条件でロードしているのかをまとめてみた。そもそも、クラスを小さく(適切な大きさに)していればClassLoaderの動きなんて調べなくてもいいはずなんだけど、そこは大人の事情ってやつで。

ちなみに、ホントに細かい動作とかは使ってるVMとかに影響されるので100%これが正しいってわけではないと思います。
まぁ、参考までに結構動きが変わるってことがわかればいいかなと

調査方法

基本的には、事あるごとにClassLoaderからロード済みのクラスをSystem.outして、どんな感じでロードしているかを調べてみた。

流れとしては、

  1. Hogeインスタンスを生成
  2. Hoge#getBuzz(int)を1-8まで実行

で、それぞれgetBuzz(int)から先の動きは以下のとおり

int 返り値 クラス名 インスタンス生成方法
0 (Buzz0) Buzz0 コンストラクタを直接
1 Buzz Buzz1 Buzz1.getInstance()内でBuzz1のコンストラクタ生成
2 Buzz2 Buzz2 Buzz2.getInstance()内でBuzz2のコンストラクタ生成
3 Buzz Buzz3 Hoge#createBuzz3()内でBuzz3のコンストラクタ生成
4 Buzz4 Buzz4 Hoge#createBuzz4()内でBuzz4のコンストラクタ生成
5 Buzz Buzz5 Factory.createBuzz5()内でBuzz5のコンストラクタ生成
6 Buzz6 Buzz6 Factory.createBuzz6()内でBuzz6のコンストラクタ生成
7 Buzz Buzz7 リフレクションによる生成
8 Buzz8 Buzz8 リフレクションによる生成

結果

(0,2,4,6,8)はgetBuzz(int)の返り値Buzzと、生成時の返り値がBuzz以外のものは、Hogeがロードされた段階で(今回はHoge内にmainがあるのでまとめて呼ばれてしまっている)getBuzz(int)内で返される可能性のあるものすべてがクラスロードされている。
(3)も基本的にはHogeのクラスロード時に読み込まれるけど、メソッドが別なのでワンテンポ遅れてるっぽい
(1,5,7)は実際にメソッドがキックされたときにクラスがロードされている
(7)は生成はしているはずなのにClassLoader内に含まれていない(謎)

まとめ

リフレクションは別格だとしても、返り値は具象クラスを指定するより、抽象クラスやインターフェースを指定したほうが、最小限のクラスロードですみそう。
そのために、ファクトリーメソッドを用意してメソッド内のクラスの粒度(?)をそろえるのが良さそう。*2

以下、参考資料

環境

JDK1.6

出力結果

### init     ####
class limelabo.Hoge
class limelabo.Hoge$Buzz
class limelabo.Hoge$Buzz0
class limelabo.Hoge$Buzz2
class limelabo.Hoge$Buzz4
class limelabo.Hoge$Buzz6
class limelabo.Hoge$Buzz8
class limelabo.Hoge$Buzz3
#### created  ####
class limelabo.Hoge
class limelabo.Hoge$Buzz
class limelabo.Hoge$Buzz0
class limelabo.Hoge$Buzz2
class limelabo.Hoge$Buzz4
class limelabo.Hoge$Buzz6
class limelabo.Hoge$Buzz8
class limelabo.Hoge$Buzz3
#### getBuzz0(limelabo.Hoge$Buzz0) ####
class limelabo.Hoge
class limelabo.Hoge$Buzz
class limelabo.Hoge$Buzz0
class limelabo.Hoge$Buzz2
class limelabo.Hoge$Buzz4
class limelabo.Hoge$Buzz6
class limelabo.Hoge$Buzz8
class limelabo.Hoge$Buzz3
#### getBuzz1(limelabo.Hoge$Buzz1) ####
class limelabo.Hoge
class limelabo.Hoge$Buzz
class limelabo.Hoge$Buzz0
class limelabo.Hoge$Buzz2
class limelabo.Hoge$Buzz4
class limelabo.Hoge$Buzz6
class limelabo.Hoge$Buzz8
class limelabo.Hoge$Buzz3
class limelabo.Hoge$Buzz1
#### getBuzz2(limelabo.Hoge$Buzz2) ####
class limelabo.Hoge
class limelabo.Hoge$Buzz
class limelabo.Hoge$Buzz0
class limelabo.Hoge$Buzz2
class limelabo.Hoge$Buzz4
class limelabo.Hoge$Buzz6
class limelabo.Hoge$Buzz8
class limelabo.Hoge$Buzz3
class limelabo.Hoge$Buzz1
#### getBuzz3(limelabo.Hoge$Buzz3) ####
class limelabo.Hoge
class limelabo.Hoge$Buzz
class limelabo.Hoge$Buzz0
class limelabo.Hoge$Buzz2
class limelabo.Hoge$Buzz4
class limelabo.Hoge$Buzz6
class limelabo.Hoge$Buzz8
class limelabo.Hoge$Buzz3
class limelabo.Hoge$Buzz1
#### getBuzz4(limelabo.Hoge$Buzz4) ####
class limelabo.Hoge
class limelabo.Hoge$Buzz
class limelabo.Hoge$Buzz0
class limelabo.Hoge$Buzz2
class limelabo.Hoge$Buzz4
class limelabo.Hoge$Buzz6
class limelabo.Hoge$Buzz8
class limelabo.Hoge$Buzz3
class limelabo.Hoge$Buzz1
#### getBuzz5(limelabo.Hoge$Buzz5) ####
class limelabo.Hoge
class limelabo.Hoge$Buzz
class limelabo.Hoge$Buzz0
class limelabo.Hoge$Buzz2
class limelabo.Hoge$Buzz4
class limelabo.Hoge$Buzz6
class limelabo.Hoge$Buzz8
class limelabo.Hoge$Buzz3
class limelabo.Hoge$Buzz1
class limelabo.Hoge$Facotory
class limelabo.Hoge$Buzz5
#### getBuzz6(limelabo.Hoge$Buzz6) ####
class limelabo.Hoge
class limelabo.Hoge$Buzz
class limelabo.Hoge$Buzz0
class limelabo.Hoge$Buzz2
class limelabo.Hoge$Buzz4
class limelabo.Hoge$Buzz6
class limelabo.Hoge$Buzz8
class limelabo.Hoge$Buzz3
class limelabo.Hoge$Buzz1
class limelabo.Hoge$Facotory
class limelabo.Hoge$Buzz5
#### getBuzz7(limelabo.Hoge$Buzz7) ####
class limelabo.Hoge
class limelabo.Hoge$Buzz
class limelabo.Hoge$Buzz0
class limelabo.Hoge$Buzz2
class limelabo.Hoge$Buzz4
class limelabo.Hoge$Buzz6
class limelabo.Hoge$Buzz8
class limelabo.Hoge$Buzz3
class limelabo.Hoge$Buzz1
class limelabo.Hoge$Facotory
class limelabo.Hoge$Buzz5
class limelabo.Hoge$Buzz7

ソースコード

package limelabo;

import java.lang.reflect.Field;
import java.util.Vector;

public class Hoge {

    public static void main(String[] args) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        printClassLoader("#### init     ####", cl);
        Hoge hoge = new Hoge();
        printClassLoader("#### created  ####", cl);

        for (int i = 0; i < 8; i++) {
            Buzz buzz = hoge.getBuzz(i);
            printClassLoader("#### getBuzz" + i + "("
                    + buzz.getClass().getName() + ") ####", cl);
        }
 
    }

    private Buzz getBuzz(int i) {
        switch (i) {
        case 0:
            return new Buzz0();
        case 1:
            // 返り値はBuzz
            return Buzz1.getInstance();
        case 2:
            // 返り値はBuzz2
            return Buzz2.getInstance();
        case 3:
            // 返り値はBuzz
            return createBuzz3();
        case 4:
            // 返り値はBuzz4
            return createBuzz4();
        case 5:
            // 返り値はBuzz
            return Facotory.createBuzz5();
        case 6:
            // 返り値はBuzz6
            return Facotory.createBuzz6();
        case 7:
            // 返り値はBuzz
            return createBuzz7ByReflection();
        case 8:
            // 返り値はBuzz8
            return createBuzz8ByReflection();
        default:
            throw new IllegalArgumentException("arg is " + i);
        }
    }

    private Buzz createBuzz3() {
        return new Buzz3();
    }

    private Buzz4 createBuzz4() {
        return new Buzz4();
    }

    static abstract class Buzz {
    }

    static class Buzz0 extends Buzz {
    }

    static class Buzz1 extends Buzz {
        static Buzz getInstance() {
            return new Buzz1();
        }
    }

    static class Buzz2 extends Buzz {
        static Buzz2 getInstance() {
            return new Buzz2();
        }
    }

    static class Buzz3 extends Buzz {
    }

    static class Buzz4 extends Buzz {
    }

    static class Buzz5 extends Buzz {
    }

    static class Buzz6 extends Buzz {
    }
    
    static class Buzz7 extends Buzz {
    }
    
    static class Buzz8 extends Buzz {
    }

    static class Facotory {
        static Buzz createBuzz5() {
            return new Buzz5();
        }

        static Buzz6 createBuzz6() {
            return new Buzz6();
        }
    }

    private Buzz createBuzz7ByReflection() {
        try {
            return (Buzz) Class.forName("limelabo.Hoge$Buzz7").newInstance();
        } catch (Exception e) {
            // ignore
        }
        return null;
    }

    private Buzz8 createBuzz8ByReflection() {
        try {
            return (Buzz8) Class.forName("limelabo.Hoge$Buzz8").newInstance();
        } catch (Exception e) {
            // ignore
        }
        return null;
    }

    private static void printClassLoader(String message, ClassLoader cl) {
        System.out.println(message);
        Class<?> c = cl.getClass();
        // ClassLoaderまでさかのぼる
        for (; !ClassLoader.class.getName().equals(c.getName()); c = c.getSuperclass()) {
        }
        try {
            Field classesField = c.getDeclaredField("classes");
            classesField.setAccessible(true);
            Vector<Class> classes = (Vector<Class>) classesField.get(cl);
            for (Class loadedClass : classes) {
                System.out.println(loadedClass);
            }
        } catch (Exception e) {
            // ignore
        }
    }
}

*1:正確にはクラスが大きすぎて大量にクラスロードされているのが原因ぽい

*2:とはいっても、全部のクラスでgetInstance()みたいなのを作るのって気持ち悪い気がするんですが