JRE 與類別檔案版本


因為各種原因,你的電腦中可能不只存在一套JRE!可以試著搜尋電腦中的檔案,例如在Windows中搜尋java.exe,可能會發現有多個java.exe檔案,某些程度上,你可以將找到一個java.exe視作就是有一套JRE!

在安裝好JDK後,如果選擇一併安裝Public JRE,至少會有兩套JRE存在電腦中,一個是JDK本身的Private JRE,一個是選擇安裝的Public JRE。

既然電腦中有可能同時存在多套JRE,那麼你到底執行了哪一套JRE?在文字模式下鍵入java指令,如果設定了PATH,會執行PATH順序下找到的第一個java可執行檔,這個可執行檔所啟動的是哪套JRE?

當找到java可執行檔並執行時,會依照以下的規則來尋找可用的JRE:

  • 可否在java可執行檔資料夾下找到相關原生(Native)程式庫
  • 可否在上一層目錄中找到jre目錄

如果設定PATH包括JDK的bin目錄,執行java指令時,因為在JDK的bin中找不到相關原生程式庫,因此找上一層資料夾的jre資料夾中有無原先程式庫,於是找到的是JDK的Private JRE。

如果將PATH設定包括C:\Program Files\Java\jre8\bin,則執行java指令時,因為同一資料夾下可以找到相關原生程式庫,於是就使用C:\Program Files\Java\jre8\這個Public JRE。

在執行java指令時,可以附帶一個-version引數,這可以顯示執行的JRE版本,這是確認所執行JRE版本的一個方式。例如:

C:\workspace>java -version
java version "1.8.0-ea"
Java(TM) SE Runtime Environment (build 1.8.0-ea-b121)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b63, mixed mode)

C:\workspace>

在一個新的開發環境中,先確認版本是很重要的一件事,文字模式下若要確認JRE,可先檢查PATH路徑中的順序,再查看java -version的資訊,這些都是基本的檢查動作。如果有個需求是切換JRE,文字模式下必須設定PATH順序中,找到的第一個JRE之 bin資料夾是你想要的JRE,而不是設定CLASSPATH

如果使用新版本JDK編譯出位元碼檔案,在舊版本JRE上執行,有可能會發生以下的錯誤訊息:

C:\workspace>javac -version
javac 1.8.0-ea

C:\workspace>javac HelloWorld.java

C:\workspace>set PATH=C:\Program Files\Java\jdk1.7.0\bin;%PATH%

C:\workspace>java -version
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)

C:\workspace>java HelloWorld
Exception in thread "main" java.lang.UnsupportedClassVersionError: HelloWorld :
Unsupported major.minor version 52.0
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:14
2)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:449)
        at java.net.URLClassLoader.access\$100(URLClassLoader.java:71)
        at java.net.URLClassLoader\$1.run(URLClassLoader.java:361)
        at java.net.URLClassLoader\$1.run(URLClassLoader.java:355)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
        at sun.misc.Launcher\$AppClassLoader.loadClass(Launcher.java:308)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:482)


C:\workspace>

以上是在JDK8下編譯出位元碼,切換PATH至JDK7,使用Private JRE7執行位元碼,結果出現UnsupportedClassVersionError,並指出這個位元碼的主版本號與次版本號(major.minor)為52.0。

編譯器會在位元碼檔案中標示主版本號與次版本號,不同的版本號,位元碼檔案格式可能有所不同。JVM在載入位元碼檔案後,會確認其版本號是否在可接受的範圍,否則就不會處理該位元碼檔案。

可以使用JDK工具程式javap,確認位元碼檔案的版本號:

C:\workspace>javap -verbose HelloWorld
Classfile /C:/workspace/HelloWorld.class
  Last modified 2014/1/7; size 425 bytes
  MD5 checksum 63e47f1d243e0eb6bc952df3f6ac0d5a
  Compiled from "HelloWorld.java"
public class HelloWorld
  SourceFile: "HelloWorld.java"
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
...

可以使System.getProperty("java.class.version")取得JRE支援的位元碼版本號.,使用System.getProperty("java.runtime.version")取得JRE版本訊息。

Java Language and Virtual Machine Specifications 會列出 Java 各版本語言及 JVM 規格書,在 JVM 規格書中都會有一章 The class File Format 說明位元碼基本格式,其中在 Java SE 5.0 的 JVM 規格書中,The class File Format 底下第一個註釋談到,Sun JDK 1.0.2的JVM實作,支援的位元碼檔案版本號為45.0到45.3。1.1.X支援45.0到45.65535(向前相容)。Java 2平台支援45.0到46.0。

Java SE 5與6支援49.0到50.0;Java SE 7支援至51.0,Java SE8則支援52.0。

在編譯的時候,可以使用-target指定編譯出來的位元碼,必須符合指定平台所允許的版本號,使用-source要求編譯器檢查使用的語法不超過指定的版本。例如:

C:\workspace>javac -source 1.7 -target 1.7 HelloWorld.java
warning: [options] bootstrap class path not set in conjunction with -source 1.7
1 warning

C:\workspace>javac -bootclasspath "C:\Program Files\Java\jre7\lib\rt.jar"
-source 1.7 -target 1.7 HelloWorld.java

C:\workspace>javac -version
javac 1.8.0-ea

C:\workspace>set PATH=C:\Program Files\Java\jdk1.7.0\bin;%PATH%

C:\workspace>java -version
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)

C:\workspace>java HelloWorld
Hello World

C:\workspace>

上面這個例子指定編譯出來的位元碼檔案必須是1.7平台可接受的版本號,並檢查使用語法必須是1.7語法。在不指定-target-source的情況下,編譯器會有預設的-target值,例如JDK8預設的-target-source都是1.8,-target在指定時,值必須大於或等於-source,所以在上面,若只指定-target為1.7,就會無法通過編譯,因為-source仍是預設值1.8。

JDK8與JDK7相比,有語法上的新增,所以-source預設為1.8(-target預設為1.8)。JDK7與JDK6相比,有語法上的新增,所以-source預設為1.7(-target預設為1.7)。JDK6(-target預設為1.6)與JDK5(-target預設為1.5)則沒有語法上的新增,所以-source都預設為1.5。

在上面也看到了,如果只指定-source-target進行編譯,會出現警示訊息,這是因為編譯時預設的Bootstrap類別載入器(Class loader)。簡單來說,系統預設的類別載入器仍參考至1.8的rt.jar(也就是Java SE 8 API的JAR檔案),如果引用到一些舊版JRE沒有的新API,就會造成在舊版JRE上無法執行,最好是編譯時指定-bootclasspath,參考至舊版的rt.jar,這樣在舊版JRE執行時比較不會發生問題。

事實上,並非一定得切換PATH至較低版本的JDK或JRE,才能測試具較低版本號的類別檔案,如果你已經安裝有舊版JDK或JRE,可以在執行時使用-version引數並指定版本。例如:

C:\workspace>javac -version
javac 1.8.0-ea

C:\workspace>javac HelloWorld.java

C:\workspace>java -version:1.7 HelloWorld
Exception in thread "main" java.lang.UnsupportedClassVersionError: HelloWorld :
Unsupported major.minor version 52.0
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(Unknown Source)
        at java.security.SecureClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.access\$100(Unknown Source)
        at java.net.URLClassLoader\$1.run(Unknown Source)
        at java.net.URLClassLoader\$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher\$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)

C:\workspace>javac -bootclasspath "C:\Program Files\Java\jre7\lib\rt.jar"
-source 1.7 -target 1.7 HelloWorld.java

C:\workspace>java -version:1.7 HelloWorld
Hello World

C:\workspace>

第一次編譯時沒有指定版本,也就是使用預設的1.8位元碼檔案版本號,執行時指定1.7版本就出現了UnsupportedClassVersionError。第二次編譯時指定編譯為1.7位元碼版本號,執行時指定1.7版本就沒有問題。

如果使用-version指定的版本,實際上無法在系統上找到已安裝的JRE,則會出現以下錯誤:

C:\workspace>java -version:1.6 HelloWorld
Error: Unable to locate JRE meeting specification "1.6"

C:\workspace>