■質問1: JavaのクラスファイルがどのバージョンのJDKでコンパイルされたのか知るには、どうすればいいですか?

  • 短い答え: 「javap -v クラス名」でminor versionとmajor versionを調べ、下記の対応表で決定します。

    major.minor → JDK version
    --------
    45.3 → 1.1
    46.0 → 1.2
    47.0 → 1.3
    48.0 → 1.4
    49.0 → 1.5
    50.0 → 1.6
    51.0 → 1.7
    

    実行例)

    c:\>javap -v Foo | findstr "version:"
      minor version: 0
      major version: 51
    

■質問2: JVMの各バージョンは、どのバージョンのクラスファイルを実行できるのですか?

  • 短い答え: 次の通りです。

    JVM version → 実行可能なmajor.minorの範囲
    --------
    1.0.2 → 45.0~45.3
    1.1.X → 45.0~45.65535
    1.2 → 45.0~46.0
    1.3 → 45.0~47.0
    1.4 → 45.0~48.0
    1.5 → 45.0~49.0
    1.6 → 45.0~50.0
    1.7 → 45.0~51.0
    

■質問3: JVMがサポートしていないバージョンのクラスファイルを読み込むとどうなりますか?

  • 短い答え: java.lang.UnsupportedClassVersionErrorがスローされます。

■質問4: 古いJVMで実行できるクラスファイルを作るには、どうすればいいですか?

  • 短い答え: javacの-targetオプションで、対象のJVMバージョンを指定します。なお、新しい文法やAPIを使ったソースコードからは古いバージョンのクラスファイルを作れないので、コンパイラが受け入れ可能なソースコードバージョンも併せて-sourceオプションで指定する必要があります。

    -target → -source
    --------
    1.7 → 1.7以下
    1.6 → 1.6以下
    1.5 → 1.5以下
    1.4 → 1.4以下
    1.3 → 1.3以下
    1.2 → 1.3以下
    1.1 → 1.3以下
    

    また、古いJVMでの動作を厳密に保証するためには、-bootclasspathオプションで古いJDKのブートストラップクラスが入ったライブラリ(rt.jarとか)を指定したり、-extdirsで新しいライブラリを見ないようにしておくなどの処置が必要です。そうしないと、昔は存在しなかったメソッドがうっかり使われていてもコンパイルエラーにならず、実行時にエラーになってしまうからです。


以下、少し補足的な説明を。

Javaのバージョンと一口に言っても、実際には

  1. 文法&APIのバージョン
  2. クラスファイルフォーマットのバージョン
  3. JVMのバージョン

に分けて考えたほうが理解しやすそうです。

文法のバージョンとは、たとえば1.4でアサーションが追加されたとか、1.5でジェネリック型(総称)が追加されたとかいう類の話。またAPIのバージョンとは、1.4でString#split()が追加されたとか、1.5でString#format()が追加されたとかいう話です。あたりまえですが、新しい文法やAPIを使ったソースを古いJDKでコンパイルはできません。文法エラーとか、そんなメソッド無いよとかのエラーになってしまいます。

クラスファイルフォーマットのバージョンとは、簡単に言えばそれがどのバージョンのJDKでコンパイルされたものかを示すもの。……なんですが、最新のコンパイラでも古いフォーマットのクラスファイルを出力する機能を持っているので(クロスコンパイル)、クラスファイルフォーマットのバージョンとはjavacの-targetに何を指定したかを示すもの、と言う方が正確かもしれません。JVM仕様書の4.クラスファイルフォーマットによると、クラスファイルの最初の4バイトはマジックナンバーで0xCAFEBABE固定、次の2バイトがクラスファイルフォーマットのマイナーバージョン、次の2バイトが同メジャーバージョンを表しています。javap -vで見えるのはこれ。で、ざっくりJDKの1.xのxに44を足したのがメジャーバージョンです。JDK1.1の頃はマイナーバージョンも細かく変えていたようですが、最近は0固定みたい。質問1の答えに載せた表は、あくまで1.7のjavacに-targetオプションでJDKバージョンを指定しらどういうクラスファイルが吐かれたか、という結果をひっくり返したものなので、実際には45.0とか45.1なんかも存在するんだと思います。

JVMのバージョンは、そのものずばり、Javaバイトコードを実行する実行環境のバージョンですね。どのJVMでも自分より古いクラスファイルフォーマットは扱えますが、自分より後にできたクラスファイルフォーマットは扱えません。そういうのに遭遇すると、UnsupportedClassVersionErrorが出て止まってしまいます。

このことから、例えば1.4の文法で書いたソースを1.5向けにコンパイルして1.6環境で実行する、なんてことがあり得るので、この3つは分けて考える方がわかりやすい、というわけです。

※バージョンメモ

  • Java SE 7u5