clojureを分解する(番外編:jdkのお勉強)
昨日、幸か不幸か、Java(及び開発環境)については、何も知らないと言う事を知って
しまった。
考えてみれば、JavaなんてCを去勢したものでしょ、ぐらいの乗りでやってきたけど
いざ道を外れた事をやろうとすると、謎だらけなんだ。
まずは、JVMのスペックシートでも取ってこよう。って、いきなりそこかよ!
分からない事が有ったら、秋葉原にいる、あの人に聞けばいいんだな。
次は、しおらしく、はろーをやっておこう。由緒正しくSunへ行く。
sakae@debian:~/tmp$ javac helo.java
sakae@debian:~/tmp$ ls -l
total 8
-rw-r--r-- 1 sakae sakae 423 Mar 25 12:37 HelloWorldApp.class
-rw-r--r-- 1 sakae sakae 261 Mar 25 12:36 helo.java
sakae@debian:~/tmp$ java HelloWorldApp
Hello World!
へぇー、ソースと、出来たオブジェクトファイル名が違うよ。後で面倒が起きないように、
ソース名を、クラス名に合わせておけってか。
そんじゃ、一つのソースに、複数のクラスを定義すると、怒られるん?
sakae@debian:~/tmp$ javac helo.java
sakae@debian:~/tmp$ ls -l
total 12
-rw-r--r-- 1 sakae sakae 423 Mar 25 12:49 HelloWorldApp.class
-rw-r--r-- 1 sakae sakae 413 Mar 25 12:49 Hoge.class
-rw-r--r-- 1 sakae sakae 394 Mar 25 12:47 helo.java
ほぇー、オブジェクトファイルが、もう一つ増えちゃったよ。
sakae@debian:~/tmp$ strings Hoge.class
Code
LineNumberTable
fuga
([Ljava/lang/String;)V
SourceFile
helo.java
Hello Java!
Hoge
java/lang/Object
java/lang/System
Ljava/io/PrintStream;
java/io/PrintStream
println
(Ljava/lang/String;)V
sakae@debian:~/tmp$ hexdump -C Hoge.class
00000000 ca fe ba be 00 00 00 32 00 1d 0a 00 06 00 0f 09 |.......2........|
00000010 00 10 00 11 08 00 12 0a 00 13 00 14 07 00 15 07 |................|
00000020 00 16 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 |........()|
00000030 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e |V...Code...LineN|
00000040 75 6d 62 65 72 54 61 62 6c 65 01 00 04 66 75 67 |umberTable...fug|
00000050 61 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 |a...([Ljava/lang|
00000060 2f 53 74 72 69 6e 67 3b 29 56 01 00 0a 53 6f 75 |/String;)V...Sou|
00000070 72 63 65 46 69 6c 65 01 00 09 68 65 6c 6f 2e 6a |rceFile...helo.j|
00000080 61 76 61 0c 00 07 00 08 07 00 17 0c 00 18 00 19 |ava.............|
00000090 01 00 0b 48 65 6c 6c 6f 20 4a 61 76 61 21 07 00 |...Hello Java!..|
000000a0 1a 0c 00 1b 00 1c 01 00 04 48 6f 67 65 01 00 10 |.........Hoge...|
000000b0 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 |java/lang/Object|
000000c0 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 |...java/lang/Sys|
000000d0 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a 61 76 |tem...out...Ljav|
000000e0 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d |a/io/PrintStream|
000000f0 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 69 6e |;...java/io/Prin|
00000100 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e 74 6c |tStream...printl|
00000110 6e 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f |n...(Ljava/lang/|
00000120 53 74 72 69 6e 67 3b 29 56 00 20 00 05 00 06 00 |String;)V. .....|
00000130 00 00 00 00 02 00 00 00 07 00 08 00 01 00 09 00 |................|
00000140 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01 b1 |...........*....|
00000150 00 00 00 01 00 0a 00 00 00 06 00 01 00 00 00 05 |................|
00000160 00 09 00 0b 00 0c 00 01 00 09 00 00 00 25 00 02 |.............%..|
00000170 00 01 00 00 00 09 b2 00 02 12 03 b6 00 04 b1 00 |................|
00000180 00 00 01 00 0a 00 00 00 0a 00 02 00 00 00 07 00 |................|
00000190 08 00 08 00 01 00 0d 00 00 00 02 00 0e |.............|
0000019d
後は、JVMの仕様書を参照しつつ、解析すればいいんだな。
何となく、遊びっぽくなってきたので、軌道修正します。えーと、jdbの関連を
調べるんでした。
sakae@debian:~/tmp$ javac -g helo.java
sakae@debian:~/tmp$ ls -l
total 12
-rw-r--r-- 1 sakae sakae 534 Mar 25 13:00 HelloWorldApp.class
-rw-r--r-- 1 sakae sakae 515 Mar 25 13:00 Hoge.class
-rw-r--r-- 1 sakae sakae 394 Mar 25 12:47 helo.java
Debug用の情報が埋め込まれたようだな。
それじゃ、gdbののりで試運転。
sakae@debian:~/tmp$ jdb HelloWorldApp
Initializing jdb ...
> run
run HelloWorldApp
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: Hello World!
The application exited
あらら、終了しちゃったよ。
途中で止めるにはどうする? そりゃ、ブレークポイントをセットする。
sakae@debian:~/tmp$ jdb HelloWorldApp
Initializing jdb ...
> stop in HelloWorldApp.main
Deferring breakpoint HelloWorldApp.main.
It will be set after the class is loaded.
> run
run HelloWorldApp
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: Set deferred breakpoint HelloWorldApp.main
Breakpoint hit: "thread=main", HelloWorldApp.main(), line=13 bci=0
13 System.out.println("Hello World!"); // Display the string.
main[1] list
9 }
10
11 class HelloWorldApp {
12 public static void main(String[] args) {
13 => System.out.println("Hello World!"); // Display the string.
14 }
15 }
16
main[1] next
Hello World!
Step completed: main[1] "thread=main", HelloWorldApp.main(), line=14 bci=8
14 }
main[1] next
>
The application exited
何とかなりそうな雰囲気だな。また、既に動き出しているアプリに対しては、
jdb -attach pid で、jdbを接続出来る。これ、gdbから取り入れたの?
それじゃ、ついでなので、jdk/binの下も見ておくかな。
sakae@debian:/usr/lib/jvm/java-6-sun-1.6.0.12/bin$ ls
ControlPanel@ java@ jdb* jstatd* rmiregistry@
HtmlConverter* java-rmi.cgi* jhat* jvisualvm* schemagen*
appletviewer* javac* jinfo* keytool@ serialver*
apt* javadoc* jmap* native2ascii* servertool@
extcheck* javah* jps* orbd@ tnameserv@
i386/ javap* jrunscript* pack200@ unpack200@
idlj* javaws@ jsadebugd* policytool@ wsgen*
jar* jconsole* jstack* rmic* wsimport*
jarsigner* jcontrol@ jstat* rmid@ xjc*
いろいろあって分からないので、雰囲気から面白そうなものを拾って(man)みた。
javap - The Java Class File Disassembler
jhat - Java Heap Analysis Tool
jmap - Memory Map
jps - Java Virtual Machine Process Status Tool
jstack - Stack Trace
jstat - Java Virtual Machine Statistics Monitoring Tool
後で気がついたけど
ここに詳しい説明が有りました。
それじゃ、java -jar clojure.jar しておいて、別窓から使えそうなコマンドを実行してみよう。
sakae@debian:~$ jps
2697 Jps
2646 jar
sakae@debian:~$ jmap 2646
Attaching to process ID 2646, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 11.2-b01
0x06000000 5298K /usr/lib/jvm/java-6-sun-1.6.0.12/jre/lib/i386/client/libjvm.so
0x08048000 46K /usr/lib/jvm/java-6-sun-1.6.0.12/jre/bin/java
0xb7c5c000 74K /usr/lib/jvm/java-6-sun-1.6.0.12/jre/lib/i386/libzip.so
0xb7c6d000 184K /usr/lib/jvm/java-6-sun-1.6.0.12/jre/lib/i386/libjava.so
0xb7c92000 55K /usr/lib/jvm/java-6-sun-1.6.0.12/jre/lib/i386/libverify.so
0xb7c9e000 41K /lib/i686/cmov/libnss_files-2.7.so
0xb7caa000 37K /lib/i686/cmov/libnss_nis-2.7.so
0xb7cb5000 85K /lib/i686/cmov/libnsl-2.7.so
0xb7ce1000 29K /lib/i686/cmov/librt-2.7.so
0xb7d3b000 145K /lib/i686/cmov/libm-2.7.so
0xb7d63000 1380K /lib/i686/cmov/libc-2.7.so
0xb7ebe000 9K /lib/i686/cmov/libdl-2.7.so
0xb7ec2000 37K /usr/lib/jvm/java-6-sun-1.6.0.12/jre/lib/i386/jli/libjli.so
0xb7ecb000 113K /lib/i686/cmov/libpthread-2.7.so
0xb7ee5000 29K /lib/i686/cmov/libnss_compat-2.7.so
0xb7eee000 37K /usr/lib/jvm/java-6-sun-1.6.0.12/jre/lib/i386/native_threads/libhpi.so
0xb7efa000 110K /lib/ld-2.7.so
これは、きっと Linux上に占めるJVM一式を表わしているんだな。仮想マシンのコードが
大きいなあ。
sakae@debian:~$ jstack 2646
2009-03-25 13:52:48
Full thread dump Java HotSpot(TM) Client VM (11.2-b01 mixed mode, sharing):
"Attach Listener" daemon prio=10 tid=0x08acfc00 nid=0xae3 waiting on condition [0x00000000..0x00000000]
java.lang.Thread.State: RUNNABLE
"Low Memory Detector" daemon prio=10 tid=0x08a7e000 nid=0xa5d runnable [0x00000000..0x00000000]
java.lang.Thread.State: RUNNABLE
"CompilerThread0" daemon prio=10 tid=0x08a7b000 nid=0xa5c waiting on condition [0x00000000..0xb59d5ae8]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" daemon prio=10 tid=0x08a79800 nid=0xa5b runnable [0x00000000..0xb5a26b90]
java.lang.Thread.State: RUNNABLE
"Finalizer" daemon prio=10 tid=0x08a71800 nid=0xa5a in Object.wait() [0xb5a77000..0xb5a77f30]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x8c4e6900> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116)
- locked <0x8c4e6900> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)
"Reference Handler" daemon prio=10 tid=0x08a70000 nid=0xa59 in Object.wait() [0xb5ac8000..0xb5ac8db0]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x8c4e6988> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:485)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
- locked <0x8c4e6988> (a java.lang.ref.Reference$Lock)
"main" prio=10 tid=0x08a47800 nid=0xa57 runnable [0xb7d39000..0xb7d3a1f8]
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:199)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:256)
at java.io.BufferedInputStream.read(BufferedInputStream.java:317)
- locked <0x8c4e91c8> (a java.io.BufferedInputStream)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)
- locked <0x8c505238> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:167)
at java.io.BufferedReader.fill(BufferedReader.java:136)
at java.io.BufferedReader.read(BufferedReader.java:157)
- locked <0x8c505238> (a java.io.InputStreamReader)
at java.io.LineNumberReader.read(LineNumberReader.java:108)
- locked <0x8c505238> (a java.io.InputStreamReader)
at java.io.FilterReader.read(FilterReader.java:48)
at java.io.PushbackReader.read(PushbackReader.java:73)
- locked <0x8c503ba0> (a java.io.LineNumberReader)
at clojure.lang.LispReader.read(LispReader.java:120)
at clojure.core$read__3694.invoke(core.clj:1805)
at clojure.core$read__3694.invoke(core.clj:1803)
at clojure.main$repl__5158$fn__5164.invoke(main.clj:83)
at clojure.main$repl__5158$fn__5173.invoke(main.clj:97)
at clojure.main$repl__5158.doInvoke(main.clj:96)
at clojure.lang.RestFn.invoke(RestFn.java:426)
at clojure.main$repl_opt__5198.invoke(main.clj:153)
at clojure.main$_main__5222.doInvoke(main.clj:228)
at clojure.lang.RestFn.invoke(RestFn.java:402)
at clojure.lang.AFn.applyToHelper(AFn.java:172)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.main.main(Unknown Source)
"VM Thread" prio=10 tid=0x08a6e400 nid=0xa58 runnable
"VM Periodic Task Thread" prio=10 tid=0x08a91c00 nid=0xa5e waiting on condition
JNI global references: 852
おお、見覚えがあるものが ...
sakae@debian:~$ jstat -gcutil 2646 1000 6
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 57.75 8.02 17.21 24.87 17 0.515 0 0.000 0.515
0.00 57.75 8.02 17.21 24.87 17 0.515 0 0.000 0.515
0.00 57.75 8.02 17.21 24.87 17 0.515 0 0.000 0.515
0.00 57.75 8.02 17.21 24.87 17 0.515 0 0.000 0.515
0.00 57.75 8.02 17.21 24.87 17 0.515 0 0.000 0.515
0.00 57.75 8.02 17.21 24.87 17 0.515 0 0.000 0.515
これは、Javaで有名なガベコレ状態のモニターっぽい。(上記は、1000ms間隔で、6
回収集)
jdkコマンドツール編の最後は、みんな大好き、逆アセンブラー
sakae@debian:~/tmp$ javap -c HelloWorldApp
Compiled from "helo.java"
class HelloWorldApp extends java.lang.Object{
HelloWorldApp();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3; //String Hello World!
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
これは、いいなあ。JVMの機械語勉強の友になるぞ!!