高度な検索
Developer Connection
Member Login ログイン | ご入会 ADC連絡先


Technote 1154

Debugging Java Code With MacsBug


目次

MacsBug 概説

「mrj」dcmd

基本コマンド

デバッグビルドで利用可能なコマンド

オブジェクトの検査

スタッククロールとスレッド表示の解釈

コンテキストの切り替え

参考文献

MacsBug は Mac OS の低レベルデバッガです。Java などの非常に高水準な言語のデバッグには役に立たないと思われるかもしれません。実はその反対なのです。MRJ の MacsBug 対応プラグイン「dcmd」をインストールすると、デッドロックからメモリリークまであらゆるものをデバッグできる数々のコマンドが追加されます。



MacsBug 概説

Java のデバッグで MacsBug を使用するには、まず MacsBug をインストールして操作の基本を学ぶ必要があります。これまでに Mac ソフトウェアの開発経験がおありなら、MacsBug はすでにご存じでしょうから、次の節まで読み飛ばしてください。

しかしながら、Macintosh で Java の開発やテストを行う人のなかには、本来の Mac デベロッパではなく、A トラップと「A 列車で行こう」の A トレインの違いがわからない人もいます。この節はこうした人のために書きました。このため他のテクニカルノートとは異なり、Mac の技術知識の多くは前提としません。

MacsBug とは

MacsBug はアップルが開発した Mac OS で動作するアセンブリ言語レベルのデバッガで、680x0 と PowerPC に対応しています。アプリケーションからドライバまで、その中間のものも含めて、大半の実行環境で動作するコードのデバッグに利用できます。Mac OS のシステムソフトウェアのデベロッパのほか、サードパーティのデベロッパがバグ報告の手段として使うこともあります。

MacsBug が認識するのはアセンブリ言語レベルの命令だけで、ソースコードは一切感知しません。高水準言語のデータ構造については初歩的なサポートしかありません。しかしマシンの状態を正確に調べるのにはすばらしいツールです。

他の大半のプラットフォームのデバッガとは異なり、MacsBug はいったんインストールされるとずっとメモリに留まります。クラッシュが起こったり、ホットキーを押すと、すぐに制御を獲得します。たとえマシンがフリーズしてしまうような状況でも MacsBug は動作します。

ダウンロードとインストール

MacsBug はアップルのデベロッパー Web サイトから入手できます。複雑な開発ツールはどれもそうですが、正式リリース前に何度かプレリリース版として提供されます。また、最新最高のバージョンはほとんど例外なくアルファ版やベータ版です。しかしながら、最新のプレリリース版をインストールするのがベストです。安定度が非常に高いことがわかっているからです。(このテクニカルノートの執筆時点では、OS 8.5 対応のものはプレリリース版しかありません。)

ダウンロード可能なファイルはたくさんありますが、必要なのは MacsBug ひとつだけです。「into System folder」というフォルダにあるはずです。システムフォルダ (機能拡張フォルダではありません) にこれをドラッグしてマシンを再起動してください。システムのブート時に、Mac OS のスプラッシュ画面の「Welcome To Mac OS」の下に「Debugger Installed」という表示が出るはずです。

簡単な概要

MacsBug のマニュアルもオンラインで提供されています。しかし分量が多く、読むのがたいへんですので (おまけにやや古い情報もあるため)、Java のみのデベロッパはもてあますかもしれません。このためここに簡単な入門編を掲載します。

MacsBug はブートプロセスの初期にメモリにロードされ、必要になるまで隠れています (多少の RAM は消費しますが)。MacsBug は次の 3 つの状況で登場します。

  1. CPU 例外やシステムエラーの発生。いずれもふつうは「クラッシュ」と呼ばれます。MacsBug がインストールされていない場合に例外やエラーが発生すると、「予期せぬ終了」アラートやあるいはより深刻な爆弾ボックスが表示されます。

  2. システムルーチンの Debugger または DebugStr の呼び出し。MacsBug は通常メッセージとともにユーザブレークを報告します。この方法でソフトウェアから危険あるいは予期せぬ状況であることを警告するメッセージをユーザに通知できます。ユーザブレークは特殊なデバッグバージョンのソフトウェアしか使うべきではありません。

  3. 四つ葉の印の付いた Command キーを押しながら電源キー (マシンを経ち上げるときに押す三角印の付いたキー) を同時に押して、ユーザ自身で MacsBug を起動します。コンピュータがフリーズ状態でもこの方法は有効です。もし起動できないとすれば、それは最悪の事態で、Command キーと Control キーと電源キーを同時に押して強制再起動を行うしかありません (「三本指のご挨拶」と呼ばれる壊滅的な状況です)。

MacsBug が登場すると画面と CPU を完全に独占します。MacsBug が表示されているあいだは、ほかのどのアプリケーションも OS ソフトウェアも動作することができません。MacsBug のユーザインタフェースが全く Mac らしくないのはこのためです。MacsBug はツールボックスのどのルーチンも使うことができないのです。

MacsBug は、DOS や UNIX のシェルと同様のコマンドライン環境です。画面の中央に固定サイズのウィンドウが 1 個表示されます。そのウィンドウの外側の部分にはゴミのピクセルが表示されています。一番下の行が入力行で、その上に機械語コードを逆アセンブルしたものが数行表示されています。その上には大きなスクロール可能な出力領域があります。左側にはレジスタとスタックの内容が表示されています。

コマンドは、最下行の入力行にタイプし、リターンキーを押して実行します。マウスでテキストを選択することはできませんが、大半の標準的な編集キーは使用できます (矢印キー、Delete キー、前方 Delete キー、Option キーと矢印キーの組み合わせ、Command キーと矢印キーの組み合わせなど)。Command-V で直前のコマンドを入力行に呼び出すことができます。

重要コマンド

MacsBug で最も重要なコマンドは次のとおりです。

  • help -- ヘルプオプション一覧を表示します。「help」の後にコマンドや項目の名前をタイプすると詳しい情報が表示されます。

  • g -- 「go」の「g」です。通常動作への復帰を指示します。これは、Command キー+電源キーで故意に MacsBug に入った場合とユーザブレークの後でしか動作しません。システムがクラッシュした後は通常動作は望めません。

  • es -- 「Exit to Shell」です。現在アクティブなアプリケーションの強制終了を指示します。MacsBug を持たないユーザが使用する Command+Option+Escape によるパニックボタンと全く同じです。これはシステムの破壊程度によりうまくいく場合とそうでない場合とがあります。また、MacsBug に入った時点と同じ実行位置にいるかどうかにも左右されます。コマンドが成功すればすぐ復帰します。うまく行かない場合は次の「rs」を使用してください。

  • rs -- 「ReStart」です。若干の後始末 (ディスクキャッシュの吐き出しなど) をしてからシステムを再起動 (リブート) します。Command+Control+電源キーを押すか、電源コードを引き抜くよりは穏やかな対処法で、ディスクの破損は起こりにくいです。

  • stdlog -- 現在のマシンの状態を表す大量の情報を含むテキストファイルをデスクトップに書き出します。アップルは、MRJ に関連して起こったクラッシュやユーザブレークを報告してもらう際にこの情報を求める場合があります。

  • log -- log コマンドに続けてファイル名を指定すると、以降の MacsBug 出力はそのファイルに書き込まれます。(ファイルは通常現在のアプリケーションの置かれたフォルダにできます。) ファイル名を指定しないで「log」とだけタイプするとログへの書き込みを停止します。

先頭ページに戻る


mrj」dcmd

アップルは「mrj」と呼ばれる新しいコマンドを追加する MacsBug のプラグイン (「dcmd」と呼ばれます) を作成しました。具体的に言うと、「mrj」に続けてタイプする名前に応じて呼び出される複数の新しいコマンドが追加されました。例えば、「mrj sc」は Java のスタッククロール (呼び出しに沿ってスタックフレームを表示したもの) を表示します。

この dcmdMRJ SDK に含まれており、「Tools: Other Tools: MRJ dcmd for PPC:」というフォルダにあります。「MRJ dcmd」というファイルをシステムフォルダの「初期設定 (Preferences)」フォルダの「MacsBug Preferences」フォルダに入れて再起動してください。

この dcmd を使用するには、Java アプリケーションかアプレットを実行しなければなりません。でないと、この dcmd の大半のコマンドは利用できないか、意味がありません。

重要
以下で説明するコマンドのほとんどすべてが MRJ 2.1 でしか動作しません。MRJ 2.0 SDK には古いバージョンの dcmd が含まれていますが、これには数個のコマンドしかありません。必ず MRJ 2.1 SDK の dcmd をインストールしてください。

お断り
このテクニカルノートは MRJ 2.1 についての説明です。この dcmd の機能やコマンドの詳細は、将来の JVM の改良に伴い変更される可能性があります。JVM の各所の変更に伴い、対応部分のコマンドや機能がなくなる場合もあります。

先頭ページに戻る


基本コマンド

ここでは頻繁に使用するコマンドについて説明します。(ここで紹介しないコマンドは大半が内部データ構造を参照するためのコマンドで、MRJ 自体をデバッグするためのものです。)

この dcmd に何もコマンドを指定しないで呼び出すと (つまり単に「mrj」とタイプする)、短いコマンド一覧を表示します。

Java ログ (mrjlog)

これは「stdlog」の Java 対応版です。現在の Java の状態を示すたくさんの情報をデスクトップにテキストファイルとして書き出します。「mrj」と「log」の間にはスペースを入れないでください。実はこのコマンドはマクロで、ログファイルを開いて、そこにいくつかの mrj dcmd コマンドの出力を書き込みます。

スタッククロール (mrj sc)

現在のスレッドとその Java スタックに関する情報を表示します。現在のスタックフレームが最初に表示されます。以下で詳細を説明します。

スレッドの表示 (mrj threads)

すべてのアクティブスレッドとスタックの情報を表示します。各スレッドが取得したモニタ (Java の言葉で言えば、現在 synchronized で同期が取られているオブジェクトです) も表示されます。表示の最後部には一番最後に使用したモニタのキャッシュ、その所有者、そのモニタを待つスレッドが表示されます。

デッドロックの検出 (mrj dl)

従来型のデッドロックを検出します。これは 2 つのスレッドがそれぞれ相手の所有するモニタの待ちでブロックしている状態です。デッドロック状態があると、該当のスレッドとモニタに関する情報が表示されます。

同期チェック (mrj sync)

デッドロック以外の同期に纏わる問題を検出します。何か発見されると関連情報が表示されます。それには次のものがあります。

  • 何らかのモニタを保持する (何かに対して同期を取っている) スレッドが Object.wait でブロックしている。
  • ほかのモニタを保持するスレッドが別のモニタでブロックしている。
  • モニタを保持するスレッドが (Thread.suspend で) 実行を停止している。

上記のどの状況も異常なものではありませんが、これが起こると、デッドロックに陥りやすく疑わしい状態と言えます。

リダイレククト出力 (mrj redirect ファイル名)

System.out と System.err を、指定された名前のファイルに切り替えます。「ファイル名」は絶対パスでなければなりません。「ファイル名」が指定されないと全く出力は行われません。

リダイレクトはすぐには有効になりません。状態変更のためには一定の Java コードを呼び出す必要があり、MacsBug が呼ばれた時点の JVM は通常それが可能な状態にありません。このため、次回メインスレッドが実行されるまで要求は保留されます。

Java ヒープの利用状況 (mrj chunks)

Java オブジェクトヒープおよび対応するハンドルヒープの総量、使用済み、空きの量が表示されます。ヒープ内のメモリブロック (チャンク) に関する情報も表示されます (これは通常有益な情報ではありません)。

MRJ 内の Java 以外のヒープの利用状況 (mrj alloch)

MRJ がオブジェクト保持以外の目的で使用するメモリ量を表示します。これには、.class ファイルからロードされたデータや、クラスやスレッドなどに対応付けられたデータ構造が含まれます。

モニタの検索 (mrj fm モニタ)

指定された ID を持つモニタに対応付けられたオブジェクトを探し、そのクラス名とハンドルを表示します。モニタの ID は mrj sc あるいは mrj threads の表示で「mon:」の箇所に表示されるものです。ハンドルが取得できれば、mrj do などのオブジェクト検査コマンドを使って、そのオブジェクトに関する詳細情報を調べることができます。

メソッドの実行 (mrj exec クラス名 メソッド名)

MRJ のメインスレッドが制御を取得するまで待ち、パラメータを持たず、返り値もない (void)、static なメソッドを呼び出します。「クラス名」は、「/」で区切ったパッケージ名の付いた完全な名前でなければなりません。クラス名とメソッド名はスペースで区切ります。「.」ではありません。

このコマンドはこのままではあまり役には立ちません。しかしご自分のアプリケーションに上記のようなメソッドを追加することで、強力なデバッグツールとなります。例えば、デフォルトのルートパッケージに Debug というクラスを追加し、それに dumpStatestartLogging といった static、void なメソッドを追加します。こうすればいつでも、Command キー+電源キーを押して「mrj exec Debug dumpState」などとタイプして、コンソールに有益な情報を表示することができるわけです。

参照の検索 (mrj fr ハンドル)

指定されたオブジェクトを参照するものを「比較的徹底的」に検索します。オブジェクトがガーベジコレクションの対象にならない理由を探るときに便利です。「比較的徹底的」といったのは、このコマンドの検索範囲はオブジェクトヒープと JNI の大域的な参照だけで、スレッドスタックは含まないためです。したがって、そのオブジェクトを参照するすべてのものが見つかるわけではありません。

参照側の情報は 1 行に 1 個ずつ表示され、「instance field」や「array element」といった説明が付きます。参照を含むオブジェクトのハンドルが表示されるので、別の mrj fr コマンドにそのハンドルを指定して、さらに深く参照関係を辿ることができます。

参照検索コマンドにはもっと複雑な mrj graphrefs もありますが、これはデバッグビルドでしか利用できません。

命令の表示 (mrj il メソッド名)

メソッドのバイトコードを逆アセンブルします。メソッドの名前は次に示すように通常の内部形式で指定してください。

  • /」で区切ったパッケージ名の付いた完全なクラス名に続けて、
  • .
  • メソッド名
  • .」と続けて、
  • 括弧のなかにエンコードされた各パラメータの型を書き、
  • それに続けてエンコードされた返り値の型を指定する。

指定例:

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 行分の情報が出力されます。これは多量の出力になります (ごく単純と思われるコードでさえたくさんの StringStringBuffer オブジェクトを生成します)。しかしオブジェクトの使用に関するコードの効率を測るにはたいへん便利な方法です。

ガーベジコレクションのトレース (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 つの方法があります。

  1. dcmd コマンドの多くでオブジェクトハンドルが表示されます。例えば、mrj sc はスレッドのハンドルや、それぞれのスタックフレームの受け手 (つまり「this」) のハンドルを表示します。

  2. Object.hashCode メソッドは具合よくオブジェクトハンドルを右に 3 ビットシフトした値を返します。したがって例えば foo のハンドルをコンソールに表示するには次のようにします。

    System.out.println(Integer.toHexString(foo.hashCode()<<3));

    残念ながら該当のオブジェクトのクラスで hashCode が別の値を返すようオーバライドされているとハンドルは取得できません。したがって、StringPoint には使えません。しかし、大半のクラスではこのコードを使って必要なオブジェクトのハンドル値をコンソールに出力することができます。

    このふるまいは JVM の改版で変更される可能性があります。

オブジェクトの表示 (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 オブジェクトの内容を表示する簡便な方法です。(doda コマンドで char[] 型の配列として表示することもできますが、面倒です。)

このコマンドは ASCII 以外の文字では役に立ちません。これを実現するには MacsBug には含まれていない高度な変換サービスが必要となるためです。

クラスの検索 (mrj fc 名前)

指定された名前のロード済みのクラスを探します。パッケージ名を含む完全なクラス名を指定してください。パッケージ名は「.」ではなく「/」で区切ってください。歴史的な事情から内部ではクラス名を「/」で区切ります。

このコマンドはクラスを見つけると、該当の Class オブジェクトのハンドルを返します。このオブジェクトの情報はそれほど役に立つものではありませんが、特定のクラスがロード済みかどうかを調べるには便利です。

クラスメソッドの表示 (mrj dcm クラス名)

指定されたクラスの全メソッドを表示します (継承されたメソッドも含みます)。クラス名は、mrj fc で述べたように「/」を使う完全な名前を指定してください。出力形式は mrj do と同様です。

表示される情報は該当のクラスとそのスーパークラス (群) に分かれます。各部分にはクラス名、そのクラスで導入された各メソッドが含まれます。各メソッドはそれぞれ 1 行ずつ次の情報が示されます。

  1. メソッド名

  2. メソッドのシグネチャ。各パラメータ型を括弧の中に書き、それに続けて返り値の型を書きます。型の表記は JVM の通常のエンコード形式を用います。つまり基本型はそれを表す 1 文字 (整数 intI、論理型 booleanZ など)、オブジェクトの場合は「L」の後ろにクラス名を付け「;」で終わります。

  3. 静的変数を表す 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*」の場合は、このスレッドは現在実行中であることを意味します。scsc7 コマンドを引数なしで実行すればそのネイティブスタックが表示されます。

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 のバグですので、stdlogmrjlog を添付してすぐにアップルに報告してください。

先頭ページに戻る


コンテキストの切り替え

実行中の MRJ インスタンスが 1 個だけの場合、mrj dcmd は自動的にそれを見つけます。MacsBug が起動された瞬間にどのアプリケーションがアクティブだったかどうかは関係ありません。しかし同時に 2 つの Java アプリケーションが実行されている場合は、読者自身がそれを指定しなければなりません。

MRJ の特定インスタンスを指定するには、その CFM コンテキスト ID を知る必要があります。これは MacsBug の「frags」コマンドでわかります。このコマンドの出力はアプリケーションの一覧を示すもので、各アプリケーションごとに利用中のライブラリ一覧が表示されます。各アプリケーション情報のヘッダ行にはそのコンテキスト ID が表示されています (ID は 10 進表示のため先頭に「#」が付きます。この値をタイプする場合はこの#を付け忘れないでください)。

コンテキストの切り替え (mrj prf コンテキスト)

指定のコンテキストで実行されている MRJ インスタンスを選びます。別のものに切り替えないかぎり、この後に実行されるコマンドはすべてこの MRJ インスタンスに適用されます。

コンテキストの表示 (mrj pr)

現在選択されている CFM コンテキストがあればそれを表示します。

先頭ページに戻る


参考文献

  • アップルコンピュータ『MacsBug Reference And Debugging Guide』1995
    MacsBug の完全なガイドです。わかりやすくはありません。大量のネイティブコードをデバッグやテストを行うのでないかぎりは不要です。
  • Lindholm, Tim 著『The Java Virtual Machine Specification』Addison-Wesley, 1996
    JVM の動作原理を完全に示した仕様書です。スレッドのふるまいについて正確に詳細を知りたい場合はこれを読んでください。(オブジェクトのレイアウトやスレッドのスケジューリングに関連する実装ごとの詳細は述べられていません。)


先頭ページに戻る