« 『mainメソッドの作成なしでのプログラムの実行』はJava7では動かなかった | トップページ | クラスのロードとイニシャライズはアトミックではない »

2011/05/18

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:

  • 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.
Invocation of certain reflective methods in class Class and in package java.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では動かなかった | トップページ | クラスのロードとイニシャライズはアトミックではない »

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/18902/51701550

この記事へのトラックバック一覧です: mainメソッドの無いクラスを実行した際にスタティックイニシャライザが呼ばれるのは仕様では無かった:

» クラスのロードとイニシャライズはアトミックではない [雪羽の発火後忘失]
『mainメソッドの作成なしでのプログラムの実行』はJava7では動かなかった: 雪羽の発火後忘失 mainメソッドの無いクラスを実行した際にスタティックイニシャライザが呼ばれるのは仕様では無かった: 雪羽の発火後忘失 と記述してきましたが、とっくの昔に説明されている方がいらっしゃいました。 「Classオブジェクトを生成済み→staticイニシャライザを実行済み」ではない - R42日記 こちら... [続きを読む]

« 『mainメソッドの作成なしでのプログラムの実行』はJava7では動かなかった | トップページ | クラスのロードとイニシャライズはアトミックではない »

other sites

  • follow us in feedly
  • github
  • stackoverflow

ソフトウェアエンジニアとして影響を受けた書籍

  • Christain Bauer: HIBERNATE イン アクション

    Christain Bauer: HIBERNATE イン アクション
    理論と実践が双方とも素晴らしい製品であるHibernate。本書はそのプロダクトを書名に冠していますが、Hibernateを使うつもりがなく、ORマッピングの解説書として読むにしても十分な良書です。Second EditionとしてJava Persistence With Hibernateという書籍も出版されていますが、残念ながら現在のところ 和訳はされていません。-インアクションは2.xの、Java Persistence-は3.1の頃のものなので、最新版とはちょっと違うところもあることに注意。 (★★★★★)

  • アンドリュー・S・タネンバウム: 分散システム 原理とパラダイム 第2版

    アンドリュー・S・タネンバウム: 分散システム 原理とパラダイム 第2版
    クライアント/サーバシステムを構築する上で必要となる知識が総論されてます。Web技術者も、フレームワーク部分を開発するのであれば必読。 (★★★★★)

  • Joel Spolsky∥著: ジョエル・オン・ソフトウェア

    Joel Spolsky∥著: ジョエル・オン・ソフトウェア
    前述の書籍「ソフトウエア開発プロフェッショナル」をより砕いたもの、という感じでしょうか。 前書きではプログラマでなくSE向けの本のように書かれているが、プログラマが読んでも面白い本であると思われます。 SEになった新人(あるいはそういう会社に入る/入りたての人)にとっては、これからどういったことが仕事を遂行していく上で起こりえるのか、どのように考えて行なっていけばいいのか決定する助けになると思います。 元は″Joel on Software″というブログの記事で、web上でも一部日本語で読めます。 http://japanese.joelonsoftware.com/ (★★★)

  • ドナルド・C・ゴース,ジェラルド・M・ワインバーグ: ライト、ついてますか

    ドナルド・C・ゴース,ジェラルド・M・ワインバーグ: ライト、ついてますか
    問題解決(一昔前のの流行語で言うところの『ソリューション』)能力は、システムエンジニアのスキルとして備えるべきもののうちのひとつです。しかし、これは難しい。学校で出されるテストと違い、唯一の、(問題提出者が想定している)解を求めるだけが「問題解決」では無いからです。そもそも、何が問題なのか、それは本当に問題なのか、それは本当に解決すべき問題なのか、その問題解決方法は正しいのか、などを解決しなければ、「その解は正しいのか」に辿りつくことができません。この本の最も良いところのひとつは、本があまり厚くないこと。すぐに読めるし、何回も読み返す気になるでしょう。 (★★★★★)

  • スティーブ・マコネル: ソフトウエア開発プロフェッショナル

    スティーブ・マコネル: ソフトウエア開発プロフェッショナル
    コードコンプリートで有名なスティーブマコネルの著書。新人SEに読んで欲しい。個人として業界の中でどうあるべきか、組織としてどうあるべきか、SEのプロ意識とは?SEの心構え概論、といったところでしょうか。また、業界における資格の重要性についても説かれています。この業界では資格が特に軽んじられる傾向がありますが、この傾向はどんな弊害をもたらすのか、将来的にこの業界は資格に対してどのような姿勢で臨んでいくべきなのか。日経BP社では(他の出版社もだが)最近、似たような類いのあまり面白くない書籍が乱出版されていますが、この本は別格だと思うので安心して購入して欲しいと思います。 (★★★★★)

無料ブログはココログ