2005年2月24日
[okita@tamkatsu-rhl9 okita]$ cat Hello.java
public class Hello {
public static void main(String [] args) {
System.out.println("Hello World");
}
}
[okita@tamkatsu-rhl9 okita]$ javac Hello.java
[okita@tamkatsu-rhl9 okita]$ ls Hello*
Hello.class Hello.java
[okita@tamkatsu-rhl9 okita]$ od -x Hello.class
0000000 feca beba 0000 2e00 1d00 000a 0006 090f
0000020 1000 1100 0008 0a12 1300 1400 0007 0715
0000040 1600 0001 3c06 6e69 7469 013e 0300 2928
0000060 0156 0400 6f43 6564 0001 4c0f 6e69 4e65
0000100 6d75 6562 5472 6261 656c 0001 6d04 6961
0000120 016e 1600 5b28 6a4c 7661 2f61 616c 676e
0000140 532f 7274 6e69 3b67 5629 0001 530a 756f
0000160 6372 4665 6c69 0165 0a00 6548 6c6c 2e6f
0000200 616a 6176 000c 0007 0708 1700 000c 0018
0000220 0119 0b00 6548 6c6c 206f 6f57 6c72 0764
0000240 1a00 000c 001b 011c 0500 6548 6c6c 016f
0000260 1000 616a 6176 6c2f 6e61 2f67 624f 656a
0000300 7463 0001 6a10 7661 2f61 616c 676e 532f
0000320 7379 6574 016d 0300 756f 0174 1500 6a4c
0000340 7661 2f61 6f69 502f 6972 746e 7453 6572
0000360 6d61 013b 1300 616a 6176 692f 2f6f 7250
0000400 6e69 5374 7274 6165 016d 0700 7270 6e69
0000420 6c74 016e 1500 4c28 616a 6176 6c2f 6e61
0000440 2f67 7453 6972 676e 293b 0056 0021 0005
0000460 0006 0000 0000 0002 0001 0007 0008 0001
0000500 0009 0000 001d 0001 0001 0000 2a05 00b7
0000520 b101 0000 0100 0a00 0000 0600 0100 0000
0000540 0100 0900 0b00 0c00 0100 0900 0000 2500
0000560 0200 0100 0000 0900 00b2 1202 b603 0400
0000600 00b1 0000 0001 000a 0000 000a 0002 0000
0000620 0003 0008 0004 0001 000d 0000 0002 000e
0000637
エンディアンに注意。
以下の文章で()内は10進数。
cafe babeは、classバイナリのシグネチャを表す。つまりクラスファイルであることを意味する。
2e00 -> 00 2e は、フォーマットのバージョン。メジャー番号00 マイナー番号0x2e(46)
0xad00 -> 0x001d (29) 定数プールのエントリ数
その後は、実際の定数プールのデータ
このクラスファイルには、このクラスとメソッドの定義だけでなく、利用される標準ライブラリの参照なども含まれる。
定数プールの次には、
クラス自体
スーパー・クラス
インタフェース
上記の定数プールのエントリを参照する項目が記述される。
次にフィールド、メソッド情報
メソッド実行コードはメソッド定義内のコード属性として含まれる。これがいわゆるバイトコード
仮想マシン[JVM]はスタック構造
第1世代 仮想マシンはインタプリタ
第2世代 JIT(Just In Time)。一番最初に実行される時に、nativecodeにコンパイルする。
次回からは、高速な処理が可能(ダイナミックコンパイラ)
第3世代 プログラムの実行を監視しコードの最適化を適宜行う。
クラス・ローディングについて
クラスのリンクは、JVMがクラスをロードする際の処理として行われる。これがC/C++との違い。
実行時バインディング(late binding)手法により、実装コードを実行まで特定せずにすむ。
JVM仕様でクラスは必要になった時に始めてロードされる。
java -verbose:class によるクラス・ローディングのトレースが可能
[okita@tamkatsu-rhl9 okita]$ cat TestMain.java
public class TestMain {
public static void main(String [] args) {
System.out.println("hello");
RecursiveObject rObject = new RecursiveObject();
rObject.print();
}
}
[okita@tamkatsu-rhl9 okita]$ cat RecursiveObject.java
public class RecursiveObject {
public void print() {
System.out.println(this.toString());
}
}
[okita@tamkatsu-rhl9 okita]$ javac TestMain.java RecursiveObject.java
[okita@tamkatsu-rhl9 okita]$ java -verbose:class -cp . TestMain
[Opened /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
[Opened /home/okita/j2sdk1.4.2_01/jre/lib/sunrsasign.jar]
[Opened /home/okita/j2sdk1.4.2_01/jre/lib/jsse.jar]
[Opened /home/okita/j2sdk1.4.2_01/jre/lib/jce.jar]
[Opened /home/okita/j2sdk1.4.2_01/jre/lib/charsets.jar]
[Loaded java.lang.Object from /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
[Loaded java.io.Serializable from /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
...
...
...
[Loaded java.security.UnresolvedPermission from /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
[Loaded java.security.BasicPermissionCollection from /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
[Loaded java.security.Principal from /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
[Loaded java.security.cert.Certificate from /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
[Loaded TestMain]
hello
[Loaded RecursiveObject]
RecursiveObject@119c082
[Loaded java.lang.Shutdown from /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
[Loaded java.lang.Shutdown$Lock from /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
再帰的にTestMainクラス, RecursiveObjectクラスをロードしているのが確認できる。
クラスのロードと初期化
バイナリー・クラス・フォーマットのデコード
他のクラスとの互換性チェック
バイトコード命令の検査
java.lang.Classインスタンス構築(新たに作成されるクラスのインスタンスの基礎となる)
クラス・ローダについて
JVMのクラス・ローディング機能。
JVMには標準ライブラリなどベースとなるクラス群をロードするブートストラップローダが存在する。
拡張クラスローダ 標準拡張ライブラリなどのロードを担当
システムクラスローダ 一般のクラスパスにおけるクラスのロードを担当
自分でクラスローダをjava.lang.ClassLoaderを継承する事で可能。
クラスローダがクラスを所有する。
各クラスローダは、親のクラスローダへの参照も記録している。ブートストラップローダがroot要素となる。
クラスローダを自作する事によりあるアプリケーションのクラスが他のアプリケーションに干渉しないようにすることが
可能である。
Tomcatのクラスローダ
[bootstrap]
-> [System]
-> [Common] TomcatとWebアプリ共通のライブラリ
-> Catalina Tomcat用のライブラリ
-> Shared Webアプリ共通のライブラリ
-> Webapp1 Webapp1用のライブラリ
-> Webapp2 Webapp2用のライブラリ
クラスローダの機能があることで、アプリケーション実行時に動的にクラスファイルをロードする事が可能。
これがあまり知られていないJavaの魅力。サーブレットなどサーバ・アプリケーションJavaで実装される理由の暗黙的な
理由の一つ。
リフレクションについて
リフレクション機能で、実行時にクラスを扱うようにコードが書ける。
[okita@tamkatsu-rhl9 okita]$ cat TestClassLoad.java
public class TestClassLoad {
public static void main(String [] args) {
System.out.println("Program start");
if ( args.length == 0 ) {
System.out.println("Program end");
return;
}
Class cls = null;
try {
// Dynamic Class Loading.
System.out.println("before Load");
cls = Class.forName(args[0]);
System.out.println("after Load");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("Program end");
}
}
[okita@tamkatsu-rhl9 okita]$ cat TestClass.java
public class TestClass {
public void print() {
System.out.println(this.toString());
}
}
[okita@tamkatsu-rhl9 okita]$ javac TestClassLoad.java TestClass.java
[okita@tamkatsu-rhl9 okita]$ java -verbose:class -cp . TestClassLoad TestClass
[Opened /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
[Opened /home/okita/j2sdk1.4.2_01/jre/lib/sunrsasign.jar]
[Opened /home/okita/j2sdk1.4.2_01/jre/lib/jsse.jar]
[Opened /home/okita/j2sdk1.4.2_01/jre/lib/jce.jar]
[Opened /home/okita/j2sdk1.4.2_01/jre/lib/charsets.jar]
[Loaded java.lang.Object from /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
...
...
[Loaded java.security.cert.Certificate from /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
[Loaded TestClassLoad]
Program start
before Load
[Loaded TestClass]
after Load
Program end
[Loaded java.lang.Shutdown from /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
[Loaded java.lang.Shutdown$Lock from /home/okita/j2sdk1.4.2_01/jre/lib/rt.jar]
TestClassクラスが、Class#forNameメソッドを実行した時にロードしているのが分かる。
つまり、実行時に必要になったときにJVMがクラスローディングを行っている。
[s-okita@localhost java]$ cat src/org/oklab/classloader/MyClassLoader.java
package org.oklab.classloader;
import java.io.IOException;
import java.io.File;
public class MyClassLoader extends ClassLoader {
private String directory;
public MyClassLoader(String dir) {
directory = dir;
}
// this method synchronizes on the classloader object
// so that multiple threads may not load the same class
// at the same time.
public synchronized Class loadClass(String name)
throws ClassNotFoundException {
Class c = findLoadedClass(name);
if (c != null) {
return c;
}
try {
c = findSystemClass(name);
return c;
} catch (ClassNotFoundException e) {
// keep looking.
}
try {
// read in the class file.
byte [] data = getClassData(directory, name);
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
byte [] getClassData(String dir, String name) throws IOException {
// ommitted for brevity.
File f = new File("hoge");
return null;
}
public static void main(String [] args) {
try {
System.out.println("start");
MyClassLoader cl = new MyClassLoader("/foo/bar");
Class stringClass = cl.loadClass("java.lang.String");
System.out.println("end");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
[s-okita@localhost java]$ java -cp class/
.svn HelloWorld.class user
ConnectionTest.class HelloWorldJNI.class util
FileLockTest.class jp
FileLockTest2.class org
[s-okita@localhost java]$ java -cp class org.oklab.classloader.MyClassLoader
start
end