| ログイン | ご入会 |
ADC連絡先
|
|
基本コマンドここでは頻繁に使用するコマンドについて説明します。(ここで紹介しないコマンドは大半が内部データ構造を参照するためのコマンドで、MRJ 自体をデバッグするためのものです。) この Java ログ (
|
il java/lang/Thread.join.()V
disassembly from $659ed84 java.lang.Thread.join(Thread.java:873)
disassembly from $659ed84 java.lang.Thread.join(Thread.java:873)
[0] 2A aload_0
[1] 9 lconst_0
[2] E2 invokevirtual_quick_w Method: "java/lang/Thread.join(J)V"
[5] B1 return
|
MRJ のデバッグビルドを使うと、MRJ dcmd のうち、利用可能なコマンドが増えます。(通常のビルドではこれらのコマンドはサポートされません。MRJ が遅くなったり、メモリを多く使用したり、その両方のことが起こりうるためです。)
デバッグビルドの場合、限定的なものですが、スレッドスケジューラにデッドロックチェック機能が組み込まれています。従来型の 2 スレッドによるデッドロックが検出されると、デッドロックを警告するユーザブレークにより、自動的に MacsBug に入ります。もしそうなったら、すぐに「mrj dl」で詳細情報を調べてください。
デバッグビルドのほかの便利な機能には、ガーベジコレクションの最中にはカーソルの形状が小さなブルドーザーになることです。この機能があると、アプリケーションが長い間停止する原因がガーベジコレクションなのかどうか判断できます (以下で述べる mrj tracegc コマンドでも同様です)。
mrj extant)現在ロードされている全クラスを英字順で表示します。クラスごとに現在のインスタンス数および最大インスタンス数を表示します。このコマンドに数値パラメータを追加すると、現在のインスタンス数がその数以上のクラスのみが表示されます。
mrj tracealloc 値)「値」が 1 だと、オブジェクト生成トレースを開始します。0 だと停止します。オブジェクトが生成されるたびにコンソールにそのオブジェクトのクラス名を含む 1 行分の情報が出力されます。これは多量の出力になります (ごく単純と思われるコードでさえたくさんの String や StringBuffer オブジェクトを生成します)。しかしオブジェクトの使用に関するコードの効率を測るにはたいへん便利な方法です。
mrj tracegc 値)「値」が 1 だとガーベジコレクションのトレースを開始します。0 だと停止します。ガーベジコレクションが起こるとその詳細を表すひとまとまりの情報が表示されます。この情報の中身はほとんど無用のものですが、ガーベジコレクションの発生を視覚的に知ることができるという点で有効です。
mrj tracem 値)「値」が 1 だとメソッドのトレースを開始します。0 だと停止します。メソッドの呼び出しと戻りで、コンソールに 1 行分の情報が出力されます。これは大量の出力を生成するため、mrj redirect を使って、コンソールウィンドウではなく、ファイルに書き出すようにしてください。プログラムを止めずに実行の流れを調べることができるので、高水準デバッガが利用できない場合などに便利です。
mrj trace 値)「値」が 1 だと命令トレースを開始します。0 だと停止します。Java バイトコード命令が実行されるたびに 1 行分の情報がコンソールに出力されます。これが有効な場合はほとんどありません。莫大な量の情報が生成されるので、まず mrj redirect で出力先をファイルに切り替えてください。JIT コンパイラが生成したコードが実行される場合は何も表示されませんので、このコマンドを使用する場合は、まず JIT をオフにしてから MRJ を起動してください。
MRJ 2.1 では表示されないバイトコードがあります。これは次のリリースで修正する予定です。
mrj graphrefs ハンドル)mrj fr よりずっと詳しい参照トレースです。参照元のオブジェクト (static な変数など) とそれが参照するオブジェクト (したがってガーベジコレクションの対象にならない) とを順に検索して表示します。
| 注意: このコマンドは拙速に作成されたため (作者の Steve Zellers はとにかくメモリリークをなくす必要があったため)、MRJ 2.1 では最小限のテストしか行われていません。Apple Applet Runner では動作しません。皮肉な話ですが、アプリケーションヒープでメモリリークを起こす場合があります。 |
このコマンドは MRJ のメインスレッドが制御を取得するまで待ち、情報を集め、結果を System.out に出力します。以下に典型的な出力内容の最初の部分を示します。
mrj graphrefs $6b11f18
recursively searching for references to $6b11f18
References to: $6b11f18
instance field: $6b11aa8 java/lang/
Thread.target(Ljava/lang/Runnable;)
instance field: $6b126c8 com/apple/mrj/console/Console$ConsoleArea.this$0(Lcom/
apple/mrj/console/Console;)
instance field: $6b13628 com/apple/mrj/
console/Console$1.this$0(Lcom/apple/mrj/console/Console;)
java thread var ref $6b11f18 at $x (tid $68ef584, )
References to: $6b11aa8
array element: $6b11ea8 [1]
c thread found $6b11aa8 at $680f2fc (tid $6adda7c, ConsoleThread)
c thread found $6b11aa8 at $680f34c (tid $6adda7c, ConsoleThread)
c thread found $6b11aa8 at $680f370 (tid $6adda7c, ConsoleThread)
c thread found $6b11aa8 at $680f3e8 (tid $6adda7c, ConsoleThread)
java thread var ref $6b11aa8 at $x (tid $68ef558, )
|
トレースは空行で区切られます。各トレースは「References to:」ではじまり、その後にトレース中のオブジェクトのハンドル、そのオブジェクトへの全参照が続きます。それには、他のオブジェクト内のインスタンス変数、静的変数、現行スタックフレーム内のローカル変数などがあります。
最初のトレース情報はコマンドで指定したオブジェクトに関するものです。後続のトレース情報は、それ以前のトレースで見つかったオブジェクトに関するものです。それぞれのトレース結果を辿ることで、問題のオブジェクトがどこから参照されているためにガーベジコレクションの対象にならないかがわかります。
この出力情報はたいへん読みづらいもので、よりわかりやすくするツールが必要だと思っています。しかしとりあえずのところは、出力をプログラムエディタに貼り付けて、検索機能を使って、該当の 16 進数を探すとよいでしょう。
Java オブジェクトまたは配列のハンドルがわかれば、オブジェクトのフィールドや配列の要素を調べることができます。ハンドルとは 32 ビットのオブジェクト ID です。ハンドルを見つけるには 3 つの方法があります。
dcmd コマンドの多くでオブジェクトハンドルが表示されます。例えば、mrj sc はスレッドのハンドルや、それぞれのスタックフレームの受け手 (つまり「this」) のハンドルを表示します。Object.hashCode メソッドは具合よくオブジェクトハンドルを右に 3 ビットシフトした値を返します。したがって例えば foo のハンドルをコンソールに表示するには次のようにします。System.out.println(Integer.toHexString(foo.hashCode()<<3));hashCode が別の値を返すようオーバライドされているとハンドルは取得できません。したがって、String や Point には使えません。しかし、大半のクラスではこのコードを使って必要なオブジェクトのハンドル値をコンソールに出力することができます。mrj do ハンドル)指定のハンドルを持つオブジェクトを表示します。結果は次のようになります。
java.lang.ThreadGroup@5D590C0/5D0A668
SuperName: java/lang/Object
# ClassName: java/lang/ThreadGroup
parent.(Ljava/lang/ThreadGroup;) = $05d540a0
name.(Ljava/lang/String;) = $05d590d0
maxPriority.(I) = 10
destroyed.(Z) = false
daemon.(Z) = false
vmAllowSuspension.(Z) = false
nthreads.(I) = 3
threads.([Ljava/lang/Thread;) = $05d59090
ngroups.(I) = 1
groups.([Ljava/lang/ThreadGroup;) = $05d5a948
# ClassName: java/lang/Object
|
最初の行にはオブジェクトのクラス名とハンドルが示されます。(「/」に続く 2 番めの数値は役に立つものではありません。) 2 番めの行にはスーパークラスの名前が示されます。
その後には、オブジェクトのクラスとそれぞれのスーパークラスの情報が続きます。各ブロックはクラス名で始まり、そのクラスで宣言されている全変数と、そのオブジェクトにおける変数の値が表示されます。(静的変数の場合最後に「[static]」が付きます。)
各行の変数情報には、変数名に続けて括弧の中に型が表示されます。型の表記は JVM の通常のエンコード形式を用います。つまり基本型はそれを表す 1 文字 (整数 int の I、論理型 boolean の Z など)、オブジェクトの場合は「L」の後ろにクラス名を付け「;」で終わります。
変数型はオブジェクトの場合、表示される値はそのハンドルです。そのオブジェクトも調べたければ、さらに mrj do コマンドを使用してください。
mrj do ハンドル)指定されたハンドルを持つ配列の内容を表示します。最初の行は配列要素の型と長さを示します。それに続けて各要素の情報がそれぞれ 1 行ずつ表示されます。
配列の一部分だけを表示するにはさらに 2 つ余分なパラメータを指定します。表示したい最初の要素のインデックスと、表示したい最後の要素の次のインデックスです。
mrj ds ハンドル)String オブジェクトの内容を表示する簡便な方法です。(do や da コマンドで char[] 型の配列として表示することもできますが、面倒です。)
このコマンドは ASCII 以外の文字では役に立ちません。これを実現するには MacsBug には含まれていない高度な変換サービスが必要となるためです。
(mrj fc 名前)指定された名前のロード済みのクラスを探します。パッケージ名を含む完全なクラス名を指定してください。パッケージ名は「.」ではなく「/」で区切ってください。歴史的な事情から内部ではクラス名を「/」で区切ります。
このコマンドはクラスを見つけると、該当の Class オブジェクトのハンドルを返します。このオブジェクトの情報はそれほど役に立つものではありませんが、特定のクラスがロード済みかどうかを調べるには便利です。
mrj dcm クラス名)指定されたクラスの全メソッドを表示します (継承されたメソッドも含みます)。クラス名は、mrj fc で述べたように「/」を使う完全な名前を指定してください。出力形式は mrj do と同様です。
表示される情報は該当のクラスとそのスーパークラス (群) に分かれます。各部分にはクラス名、そのクラスで導入された各メソッドが含まれます。各メソッドはそれぞれ 1 行ずつ次の情報が示されます。
int の I、論理型 boolean の Z など)、オブジェクトの場合は「L」の後ろにクラス名を付け「;」で終わります。static や同期を指定する synchronized などの修飾子。
mrj dom ハンドル)mrj dcm と同様ですが、指定されたハンドルを持つオブジェクトのクラスのメソッドを表示します。
mrj sc コマンドと mrj threads コマンドはいずれもスタッククロールを表示します。そこには、わかりにくいですが、有益な情報が詰め込まれています。典型的なスタッククロールの例を示します。
"QDPipeline"
TID: $60a1bf8, prio: 5
sys_thread: $5fe3200, priority: 5, saved_sp: $5fc5980
state: WCV, mon: $727ec24, cq: $727ec30
$60a1c20 -> java.lang.Object.wait(Object.java:315)
$60a1c20 -> com.apple.mrj.internal.awt.QDPipeline.run(QDPipeline.java:289)
$60a1bf8 -> java.lang.Thread.run(Thread.java:474)
|
最初の 4 行はスレッドに関する情報を表示します。
1 行目の引用符に囲まれたスレッド名:スレッドのコンストラクタに渡された String 型のパラメータです。指定しなかった場合は「Thread-7」のようなデフォルトの名前になります。スレッドに意味のある名前を付けるとデバッグがしやすくなります。
TID: スレッドオブジェクトのハンドルです。これは mrj do などのオブジェクト検査コマンドで使うことができます。
prio: 通常の Java スレッドの優先順位で、1〜10 の範囲になります。実際に調べてみると「メイン」のスレッドの優先順位が 11 になっていることに読者は、気づかれるかもしれません。プログラム側でこのように設定することは不可能です。JVM がわざとそのように設定するのです。こうする理由は、メインスレッドは JManager 呼び出しの際ほかのあらゆるスレッドに割り込む必要があるためです。
sys_thread: ネイティブ (下位システムの) スレッドの構造体を指します。ネイティブスレッドの情報は mrj thd コマンドで表示できます。わかりにくいですが大量の情報が表示されます。
saved_sp: あるいは *current thread*: 見出しが「saved_sp」の場合は、スレッドのネイティブスタックを指します。これは MacsBug の通常のスタッククロールコマンド sc7 によるスタック表示で使えます。(しかし sc7 によるスタッククロール表示はあまり正確ではなく、大量のゴミが表示される場合があります。) 見出しが「*current thread*」の場合は、このスレッドは現在実行中であることを意味します。sc や sc7 コマンドを引数なしで実行すればそのネイティブスタックが表示されます。
priority: 歴史的な事情があって、前行の「prio」という見出しの情報とだぶっています。
state: スレッドの現在の状態です。
・RDY (ready): スレッドが実行可能であることを表します。つまり現在すでに実行中か、スケジューラが選択すれば即実行状態になれるか、いずれかの状態です。(後者の状態であっても、これより優先順位の高いスレッドが常にビジー状態であれば実行のチャンスはありません。)
・WMON (waiting on monitor): たいていの場合、同期 (synchronized) の指定のあるメソッドまたは実行文にまさに入ろうとするスレッドが、そのオブジェクトとすでに同期中のスレッドがほかにあるため、待ちのブロックに入っていることを表します。(dcmd のバグのため、実行可能なスレッドなのにときたま間違えられて WMON とされる場合があります。)
・WCV (waiting on condition variable): スレッドは Object.wait メソッドでブロックしており、Object.notify または notifyAll による再開を待っている状態です。
・SUSP (suspended): スレッドは Thread.suspend で実行を保留されている状態です。
mon:このフィールドは、スレッド状態が WMON の場合、そのスレッドがブロックしているモニタの ID を示します。モニタは通常何らかのオブジェクトと対応付けられていますが、モニタ ID そのものはオブジェクトハンドルではありません。また、どのオブジェクトにも対応しないモニタも存在します。mrj fm コマンドを使うとそのモニタの所有者である Java オブジェクトがわかります。また、mrj mon コマンドでモニタ自体の情報 (わかりにくいです) を表示できる場合があります。
cq: このフィールドは、スレッド状態が WCV の場合、そのスレッドが待ち状態に置かれているコンディッションキューの ID を表します。これは内部データ構造のひとつで、そこにはプログラマが関知するような部分はありません。
スレッドヘッダの後には Java スタッククロールが続きます。これはたいていの場合、例外が起こったときにコンソールに表示されるスタッククロールと同一のものです。
スタックフレームは新しい順に表示されます。つまり現在のスレッドが一番上に来ます。
各行の最初には 16 進数が表示されます。これはメソッドの「this」変数 (受け手) のオブジェクトハンドルです。これは mrj do などのオブジェクト検査コマンドで使用できます。
オブジェクトハンドルに続いてメソッドの名前が来ます。それに続いて括弧の中に、ソースファイルの名前と発生箇所の行番号が入ります。
| 注意: クラスファイルに行番号の対応表が含まれていない場合、ソースファイル名と行番号は表示されません。(Metrowerks CodeWarrior でコードをコンパイルした場合、プロジェクトウィンドウのソースファイルのとなりにあるデバッグの印をオンにしてください。) ソースファイルと行番号の情報が取得できないとき、その部分は「(Compiled Code)」と表示されます。行番号対応表がない場合のほか、JIT コンパイラによりメソッドがネイティブコードにコンパイルされた場合も同様です。JIT が生成したネイティブな命令を Java バイトコードに戻すことは不可能か、非常にむずかしいのです。行番号を表示したければ JIT をオフにしてください。それには、「MRJ Libraries」フォルダから「MRJ PPC JITC」を削除してください。 |
モニタとはオブジェクト同期を実現する基本機構です。モニタはオブジェクトではありません。スレッドが何らかのオブジェクトと同期を取ろうとすると、そのオブジェクトにモニタが割り当てられます。(何のオブジェクトにも対応しない内部使用のモニタもあります。)
上記で述べたように、スタッククロール表示のヘッダには、そのスレッドがモニタでブロックしているかどうか、もしそうならそのモニタを表示します。おそらく読者が次に知りたいことは、そのモニタに対応するオブジェクトは何かということでしょう。たいていの場合これは (いつもとはかぎりませんが)「mrj threads」表示のモニタキャッシュ表示の部分を見ればわかります。これは最後のスレッドのスタッククロールの次に来ます。そこには、最近モニタを獲得したオブジェクトが列挙されています。典型的な表示を示します。
"com.apple.mrj.JManager.AVDispatcherThread@60A09C8/618AD48"
<unowned>
Waiting to be notified:
"AVGrp-com.apple.mrj.JManager.JMAWTContextImpl@c6ce6f-Disp" prio 4
|
最初の行はオブジェクトのクラスです。「@」と「/」に挟まれた 16 進数はオブジェクトハンドルです。これを使えば mrj do などのコマンドでオブジェクトを検査することができます。
2 行めはそのモニタを所有 (現在保持している) スレッドを示します。所有者がない場合は「<unowned>」と表示されます。
そのモニタでブロックしているスレッドが 1 つまたは複数ある場合「Waiting to be notified:」に続く行に表示されます。それぞれ、名前、それに続けて「@」、スレッドオブジェクトのハンドル、優先順位が表示されます。上で示したように各スレッドの詳細はそれぞれのスタッククロールで見ることができます。
同期に関連して起こる問題でよく出てくる Java オブジェクトが 2 つありますので、それについて説明しましょう。com.apple.mrj.macos.toolbox.Toolbox は、ツールボックスのロックで、AWT ピアなどのネイティブコードあるいは JDirect コードが Mac ツールボックスと同期を取るのに使用します (「Technote 1153:MRJ から複数スレッドで安全にツールボックスを利用する方法」参照)。モニタキャッシュに java.lang.Object がある場合、それはおそらく public な AWT クラスが使用する treeLock (java.awt.Component で宣言されています) で、コンポーネント階層へのアクセスの同期を取るのに使われます。AWT 関連のデッドロックの原因は多くの場合この treeLock が原因です。
スレッド表示の最後の部分は登録済みモニタの一覧です。モニタにはどのオブジェクトにも割り当てられてはいないが、JVM が認知しているものがあります。これらのモニタは、JIT、クラスローダ、finalize スレッドなどが内部で使用します。通常読者はこれを意識する必要はありません。しかしごくまれにこれらのモニタに関わるデッドロックが起こる場合があります (例えば、われわれは JIT とクラスローダの間で起こったデッドロックによるバグで苦しんだ経験があります)。これらに絡む問題が起こった場合はまず間違いなく MRJ のバグですので、stdlog と mrjlog を添付してすぐにアップルに報告してください。
実行中の MRJ インスタンスが 1 個だけの場合、mrj dcmd は自動的にそれを見つけます。MacsBug が起動された瞬間にどのアプリケーションがアクティブだったかどうかは関係ありません。しかし同時に 2 つの Java アプリケーションが実行されている場合は、読者自身がそれを指定しなければなりません。
MRJ の特定インスタンスを指定するには、その CFM コンテキスト ID を知る必要があります。これは MacsBug の「frags」コマンドでわかります。このコマンドの出力はアプリケーションの一覧を示すもので、各アプリケーションごとに利用中のライブラリ一覧が表示されます。各アプリケーション情報のヘッダ行にはそのコンテキスト ID が表示されています (ID は 10 進表示のため先頭に「#」が付きます。この値をタイプする場合はこの#を付け忘れないでください)。
mrj prf コンテキスト)指定のコンテキストで実行されている MRJ インスタンスを選びます。別のものに切り替えないかぎり、この後に実行されるコマンドはすべてこの MRJ インスタンスに適用されます。
mrj pr)現在選択されている CFM コンテキストがあればそれを表示します。