mainメソッドの無いクラスを実行した際にスタティックイニシャライザが呼ばれるのは仕様では無かった
昨日のエントリ
で、mainメソッドが無いクラスをjava.exeで呼んだ時の挙動がときの挙動がJava6とJava7で変わっている、と書きました。また、最後に
だからなんだ、と言われると困りますが。
というように書いたのですが、その後この一言が何故か頭から離れませんでした。
モヤモヤを解消するため考えを整理することにしました。私は昨日の時点では以下のような認識でした。
- static initializerは、クラスがロードされた時点で実行される 。
java.exe Starter
というコマンドを実行した時点では、 本当にStarterクラスにmainメソッドが含まれているかどうかわからない。- 従って結果がどうであれStarterクラスをロードする必要がある。
- 実際にはmainメソッドが含まれていなかったとわかる(NoSuchMethodError)のは、ロード後、すなわちstatic initializerが実行された後である。
しかし、上記のような仕様であるなら、Java7でも同じ挙動でなければならないはずです。この点が引っ掛かりを感じた原因でした。
そこで、実装(OpenJDK7 b142, OpenJDK6 b22)と仕様(The Java Language Specification, Third Edition)それぞれで確認してみました。
実装の部
OpenJDK7では、Mainクラスのロードと、そのクラスにmainメソッドが含まれているかのチェックをsun.launcher.LauncherHelperクラス(Enumでのシングルトンパターン実装ですね)のcheckAndLoadMainメソッド及びgetMainMethodメソッドで行っていました。以下に抜粋します(OpenJDK7 b142のソースとは同一ではないのですが意味合い的には変わらないため、上のリンク先から抜粋しています)。
1: ClassLoader loader = ClassLoader.getSystemClassLoader();
2: clazz = loader.loadClass(classname);
3:
4: try {
5: method = clazz.getMethod("main", String[].class);
6: } catch (NoSuchMethodException nsme) {
7: ostream.println(getLocalizedMessage("java.launcher.cls.error4",
8: classname));
9: throw new RuntimeException("Main method not found in " + classname);
10: }
前述した私の理解が正しいのであれば、2行目でクラスロードしたときにstatic initializerが働き文字列が出力されるはずなのですが、実際のJava7はそのような動作になっていません。
あれ?と思い、同じようにloadClassメソッドを使って昨日のStarterクラスをロードしてみました。
1: public class Main {
2: public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException,
3: IllegalArgumentException, InvocationTargetException {
4: ClassLoader ld = ClassLoader.getSystemClassLoader();
5: Class<?> c = ld.loadClass("Starter");
6: try {
7: Method method = c.getMethod("main", String[].class);
8: method.invoke(c, new String[]{});
9: } catch (NoSuchMethodException ex) {
10: System.err.println("NoSuchMethodException");
11: }
12: }
13: }
…やはり出力されません(Java6/7とも)。じゃあなぜJava6のjava.exeでは出力されているのかが気になります。
Java6で該当する処理は、java.c内のJLI_Launch関数内で実装されていました(JNIでの実装です)。こちらも大きく省略した抜粋コードを以下に示します。
1: int
2: JLI_Launch()
3: {
4: mainClass = LoadClass(env, classname);
5: /* Get the application's main method */
6: mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
7: "([Ljava/lang/String;)V");
8: }
9:
10: static jclass
11: LoadClass(JNIEnv *env, char *name)
12: {
13: cls = (*env)->FindClass(env, buf);
14: return cls;
15: }
JNIのFindClass関数でMainクラスを取得し、同じくJNIのGetStaicMethodID関数でmainメソッドを取得しています。こちらの方法でも昨日のStarterクラスをロードしてみます。
1: #include <jni.h>
2: #include <stdio.h>
3: #include <stdlib.h>
4:
5: int main(int argc, char **argv) {
6: JNIEnv *env;
7: JavaVM *jvm;
8: JNIEnv jni;
9: JavaVM vmi;
10: JavaVMInitArgs vm_args;
11: JavaVMOption options[4];
12: /* 省略 */
13:
14: JNI_CreateJavaVM(&jvm, (void **) & env, &vm_args);
15: jni = *env;
16: vmi = *jvm;
17:
18: jclass cls = jni->FindClass(env, "Starter");
19: if (cls == 0) {
20: printf("Starter Not Found.\n");
21: exit(1);
22: }
23: /*
24: jmethodID mid = jni->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
25: if (mid == 0) {
26: printf("main method Not Found.\n");
27: exit(1);
28: }
29: */
30: vmi->DestroyJavaVM(jvm);
31: }
これを実行してみると…文字列が出力されました!
FindClass関数でクラスをロードするとstatic initializerが働くようです。ただ、ドキュメントにはそういったことは書かれていないように見え、期待される動作なのかはわかりませんでした。(補足すると、この部分はJava6で試しました。Cコンパイラがある環境にJava7を導入していないもので…)
そんなわけで、昨日気付いたJava6とJava7の挙動の違いは、Bootstrap実装の差異によるもののようです。
仕様の部
仕様書ではどう書かれているのでしょうか。そのままのタイトル 12.4.1 When Initialization Occurs という節がありました。引用します。
A class or interface type T will be initialized immediately before the first occurrence of any one of the following:
Invocation of certain reflective methods in class
- T is a class and an instance of T is created.
- T is a class and a static method declared by T is invoked.
- A static field declared by T is assigned.
- A static field declared by T is used and the field is not a constant variable (§4.12.4).
- T is a top-level class, and an
assert
statement (§14.10) lexically nested within T is executed.Class
and in packagejava.lang.reflect
also causes class or interface initialization. A class or interface will not be initialized under any other circumstance.
この記述を読むと、昨日のStarterクラスでの実例のように、宣言していないstaticなmainメソッドを呼ぼうとしたときは初期化されるべき条件には当てはまらないことが分かります。また、 12.4.2 Detailed Initialization Procedure には、Classオブジェクトの状態として
This
Class
object is verified and prepared but not initialized.
という状態があることも書かれており、おそらくこの状態なのではないだろうかと思います(注:かなり端折って読んだので勘違いの可能性もあります)。
というわけで、冒頭に記載した私の認識は誤りである、つまり
- Classの初期化はクラスロード時に行われるわけではない
ということことがわかりました。そして、mainメソッドの無いクラスを指定して実行した場合にstatic initializerが実行されていないJava7の挙動が仕様から外れているわけでもなさそうです。(will notということは、上記条件に当てはまらない状況ではinitializeされない方が普通、むしろJava6の挙動がおかしい、ってことですよね?)
« 『mainメソッドの作成なしでのプログラムの実行』はJava7では動かなかった | トップページ | クラスのロードとイニシャライズはアトミックではない »
この記事へのコメントは終了しました。
トラックバック
この記事へのトラックバック一覧です: mainメソッドの無いクラスを実行した際にスタティックイニシャライザが呼ばれるのは仕様では無かった:
» クラスのロードとイニシャライズはアトミックではない [雪羽の発火後忘失]
『mainメソッドの作成なしでのプログラムの実行』はJava7では動かなかった: 雪羽の発火後忘失 mainメソッドの無いクラスを実行した際にスタティックイニシャライザが呼ばれるのは仕様では無かった: 雪羽の発火後忘失 と記述してきましたが、とっくの昔に説明されている方がいらっしゃいました。 「Classオブジェクトを生成済み→staticイニシャライザを実行済み」ではない - R42日記 こちら... [続きを読む]
« 『mainメソッドの作成なしでのプログラムの実行』はJava7では動かなかった | トップページ | クラスのロードとイニシャライズはアトミックではない »
コメント