#chiroito ’s blog

Java を中心とした趣味の技術について

Java のメモリ上にあるバイトコードをクラスファイルとして出力

開発者は Java 言語でソースコードを書き、そのソースコードは Javac というコンパイラでバイトコードに変換されクラスファイルとなり、クラスファイルは Jar ファイルとしてまとめられ、 Java コマンドでアプリケーションとして実行します。

アプリケーション上で実行されるバイトコードはコンパイルされたものと一緒であることが普通です。ASM などのバイトコードを操作するツールなど一部のフレームワークや Java の機能を使うと、このバイトコードは変更されます。操作結果を確認したい場合などそのバイトコードを見たいことは多々あると思いますので、取り出す方法を紹介します。

手順

取り出すためには JVM のデバッガである jhsdb というツールを使います。これは JDK に標準で含まれているツールです。javaコマンドと一緒に$JAVA_HOME/binに含まれています。これにhsdbという引数を付けて実行することでツールをGUIで起動します。

$ jhsdb hsdb

f:id:chiroito:20200719133335p:plain

デバッグをする方法は、プロセスへの接続、コアファイルを与えるなどいろいろあります。今回はプロセスIDを指定してプロセスへ接続します。 [File] - [Attach to HotSpot Process... Alt-A] を実行します。

f:id:chiroito:20200719133453p:plain

接続したいアプリケーションのプロセスIDを取得します。これはjhsdbと同じく Java に標準で含まれる jcmd を使うのが良いでしょう。これも$JAVA_HOME/binに含まれています。以下の様に実行すると、プロセスID と実行しているクラスが表示されます。今回はSampleApplicationへ接続しますのでプロセスIDは 82884 になります。

$ jcmd
82884 SampleApplication
157688 jdk.jcmd/sun.tools.jcmd.JCmd
75868 jdk.hotspot.agent/sun.jvm.hotspot.SALauncher hsdb

プロセスID を入力して[OK]を押すとそのプロセスへ接続します。接続が完了すると、以下の様に JVM で動いているスレッドの一覧が表示されます。

f:id:chiroito:20200719134049p:plain

今回の目的はクラスファイルを得ることです。その役割である JVM 上にロードされているクラスを見るクラスブラウザを起動しましょう。[Tools] - [Class Browser] を選択します。

f:id:chiroito:20200719134333p:plain

クラスブラウザが起動すると、JVM に読み込まれているクラスが一覧で表示されます。上の方にあるテキストフィールドに取得したいクラス名を入力することで絞ることができます。取得したいクラスが見つかったらそのクラス名をクリックします。

f:id:chiroito:20200719134555p:plain

クラスをクリックすると、そのクラスのスーパークラス、フィールド、メソッド、コンスタントプールの一覧が表示されます。 また、上の方にはクラスファイルを出力するためのCreate .class Fileがあります。これをクリックすることでクラスファイルが出力されます。

f:id:chiroito:20200719135205p:plain

出力先はjhsdbを起動しているディレクトリ直下です。パッケージ構造をディレクトリとした形で出力されます。移行した画面に表示されているクラス名を選択すると先ほどの画面に戻れます。

得られたクラスファイルは JD や IntelliJ に含まれているデコンパイラを使うことで開けます。注意したいのはフィールドやメソッドを自動生成したことを示す ACC_SYNTHETIC というフラグです。デコンパイラによってはこのフラグがあるフィールドやメソッドを無視する事もあるので注意してください。