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の機械語勉強の友になるぞ!!