Javaでのシステム開発経験がある方でも、Javaバイトコードと言われるとイマイチ理解出来ていない方も多いのではないでしょうか。
本記事では、読めなくても困らないけど仕組みとして理解しておきたいJavaバイトコードについてご紹介していきます。
Javaバイトコードとは
Javaバイトコードは、Java仮想マシーン(Java Virtual Machine)で実行するために生成される中間コードのことを指します。
概要
Java言語では、JVMと呼ばれる仮想マシーン上でプログラムを実行するためにJavaバイトコードと呼ばれる中間コードへ一度変換した上で、他の言語同様にコンピューターが理解するための機械語へ変換が行われます。
機械語と同じくJavaバイトコードもバイナリのため、Java言語のように人が読めるよう作成された高級言語とは異なります。
Javaでコーディングを行う際「.java」の拡張子を利用しますが、このJavaバイトコードに変換されたのが「.class」ファイルの状態です。
Javaバイトコードを読める必要はない
Javaでのプログラミングを学習する上で、Javaバイトコードを読めるようになる必要はありません。
あくまでJava言語でコーディングを行い、JVM上で実行するために変換されるのがJavaバイトコードだからです。
Javaバイトコードを直接編集する必要はなく、プログラマが実装するのは「.java」拡張子で作成するJavaバイトコードへの変換前プログラムです。
Javaのバイトコードを確認してみよう
ここからは実際にJavaのバイトコードを作成して、ファイルの中身を確認してみましょう。
Javaファイルからclassファイルを作成
サンプルでは、「Main.java」ファイルを下記の内容で作成しました。
public class Main { public static void main(String[] args) { String name = "ポテパン"; System.out.println(name + "サンプル"); } }
ファイルが格納されたディレクトリで下記コマンドを実行し、コンパイルします。
javac Main.java
Javaファイルと同一階層を確認してみると「Main.class」ファイルが作成されていることが分かります。
ls Main.class Main.java
Javaバイトコードを開いてみよう
Javaバイトコードであるclassファイルはテキストエディタで開くことが可能です。
作成されたMain.classの一部を掲載しておきます。
java/lang/Object()Vポテパン java/lang/SystemoutLjava/io/PrintStream;makeConcatWithConstants&(Ljava/lang/String;)Ljava/lang/String; java/io/PrintStreamprintln(Ljava/lang/String;)VMainCodeLineNumberTablemain([Ljava/lang/String;)V SourceFile Main.javaBootstrapMethods#
このように一部理解出来そうな部分もありますが、JVM実行用のJavaバイトコードとして変換されていることが分かります。
バイナリダンプで出力
次は「hexdump」コマンドでclassファイルのダンプを確認してみます。
$ hexdump -C Main.class 00000000 ca fe ba be 00 00 00 3d 00 30 0a 00 02 00 03 07 |.......=.0......| 00000010 00 04 0c 00 05 00 06 01 00 10 6a 61 76 61 2f 6c |..........java/l| 00000020 61 6e 67 2f 4f 62 6a 65 63 74 01 00 06 3c 69 6e |ang/Object......()V.......| 00000040 83 9d e3 83 86 e3 83 91 e3 83 b3 09 00 0a 00 0b |................| 00000050 07 00 0c 0c 00 0d 00 0e 01 00 10 6a 61 76 61 2f |...........java/| 省略
開発者が読み解くには難しい状態ですが、最初の「ca fe ba be」の部分がクラスファイルを識別する「マジックナンバー」、次の「00 00 00 3d」の部分が「バージョン情報」という風にコンピューターが読み取れるように意味合いが与えられています。
Javaバイトコードを逆アセンブルしてみよう
Javaバイトコードであるclassファイルは、逆アセンブルすることにより開発者にも読みやすい形式に変換することが可能です。
他の開発者が作成されたプログラムでclassファイルしか入手出来なかった場合などにも使える方法ですので試してみてください。
javapコマンド
Javaバイトコードを逆アセンブルするには「javap」コマンドを利用するのが手軽です。
様々なオプションが用意されており、Javaバイトコードの知りたい情報をピックアップして確認することが出来ます。
詳細は「javap -help」コマンドで確認してみてください。
クラスファイルの詳細情報を表示
では実際に上記で作成したMain.classファイルを逆アセンブルしてみたいと思います。
javap -v Main.class Classfile /Users/ユーザー名/Documents/java/sample1/Main.class Last modified 2021/12/26; size 884 bytes SHA-256 checksum xxx Compiled from "Main.java" public class Main minor version: 0 major version: 61 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #25 // Main super_class: #2 // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 3 Constant pool: #1 = Methodref #2.#3 // java/lang/Object."":()V #2 = Class #4 // java/lang/Object #3 = NameAndType #5:#6 // "":()V #4 = Utf8 java/lang/Object #5 = Utf8 #6 = Utf8 ()V #7 = String #8 // ポテパン #8 = Utf8 ポテパン #9 = Fieldref #10.#11 // java/lang/System.out:Ljava/io/Print 省略 Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: ldc #7 // String ポテパン 2: astore_1 3: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 6: aload_1 7: invokedynamic #15, 0 // InvokeDynamic #0:makeConcatWith
クラスファイルのパスやチェックサム、Constant poolやCodeの内容などクラスファイルから読み取ることが出来る詳細な内容が表示されます。
Javaバイトコードの命令のみを表示
次に作成したJavaバイトコードの部分のみが知りたいんだという場合には「-c」オプションを付与することで簡単に抜き出すことが可能です。
$ javap -c Main.class Compiled from "Main.java" public class Main { public Main(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #7 // String ポテパン 2: astore_1 3: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 6: aload_1 7: invokedynamic #15, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String; 12: invokevirtual #19 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 15: return }
「-v」コマンドでも表示される内容ですが、Javaバイトコードの命令だけが抽出されていることが確認出来ます。
さいごに: Javaバイトコードは読めなくても問題ない!
本記事では、Javaバイトコードについて基本的な情報から実際に記述されている内容の確認方法までご紹介してきました。
Javaバイトコード自体は開発者が読める必要は全くありません。
ただし、Java言語の仕組みやクラスファイルから逆アセンブルするような場合には意味合いを理解しておく必要がありますので、今回の情報を参考にJavaバイトコードの知識を頭の片隅に残しておきましょう。
javapコマンド自体はOpenJDKに付属するコマンドで、ご利用のJavaによっては利用出来ない可能性もあります。