在裝2個(gè)不同版本JDK時(shí)遇到了這個(gè)問(wèn)題,在網(wǎng)上鉤了一吧!查到一個(gè)講解比較好的資料。
一:要解決的問(wèn)題
我們?cè)趪L鮮 JDK1.5 的時(shí)候,相信不少人遇到過(guò) Unsupported major.minor version 49.0 錯(cuò)誤,當(dāng)時(shí)定會(huì)茫然不知所措。因?yàn)閯傞_(kāi)始那會(huì)兒,網(wǎng)上與此相關(guān)的中文資料還不多,現(xiàn)在好了,網(wǎng)上一找就知道是如何解決,大多會(huì)告訴你要使用 JDK 1.4 重新編譯。那么至于為什么,那個(gè) major.minor 究竟為何物呢?這就是本篇來(lái)講的內(nèi)容,以使未錯(cuò)而先知。
我覺(jué)得我是比較幸運(yùn)的,因?yàn)樵谟龅侥莻€(gè)錯(cuò)誤之前已研讀過(guò)《深入 Java 虛擬機(jī)》第二版,英文原書(shū)名為《Inside the Java Virtual Machine》( Second Edition),看時(shí)已知曉 major.minor 藏匿于何處,但沒(méi)有切身體會(huì),待到與 Unsupported major.minor version 49.0 真正會(huì)面試,正好是給我驗(yàn)證了一個(gè)事實(shí)。
首先我們要對(duì) Unsupported major.minor version 49.0 建立的直接感覺(jué)是:JDK1.5 編譯出來(lái)的類不能在 JVM 1.4 下運(yùn)行,必須編譯成 JVM 1.4 下能運(yùn)行的類。(當(dāng)然,也許你用的還是 JVM 1.3 或 JVM 1.2,那么就要編譯成目標(biāo) JVM 能認(rèn)可的類)。這也解決問(wèn)題的方向。
二:major.minor 棲身于何處
何謂 major.minor,且又居身于何處呢?先感性認(rèn)識(shí)并找到 major.minor 來(lái)。
寫(xiě)一個(gè) Java Hello World! 代碼,然后用 JDK 1.5 的編譯器編譯成,HelloWorld.java
1
2
3
4
5
6
7
8
9
|
package com.unmi; public class HelloWorld { public static void main(String[] args) { System.out.println( "Hello, World!" ); } } package com.unmi;public class HelloWorld{ public static void main(String[] args) { System.out.println( "Hello, World!" ); }} |
用 JDK 1.5 的 javac -d . HelloWorld.java 編譯出來(lái)的字節(jié)碼 HelloWorld.class 用 UltraEdit 打開(kāi)來(lái)的內(nèi)容如圖所示:
從上圖中我們看出來(lái)了什么是 major.minor version 了,它相當(dāng)于一個(gè)軟件的主次版本號(hào),只是在這里是標(biāo)識(shí)的一個(gè) Java Class 的主版本號(hào)和次版本號(hào),同時(shí)我們看到 minor_version 為 0x0000,major_version 為 0x0031,轉(zhuǎn)換為十制數(shù)分別為0 和 49,即 major.minor 就是 49.0 了。
三:何謂 major.minor 以及何用
Class 文件的第 5-8 字節(jié)為 minor_version 和 major_version。Java class 文件格式可能會(huì)加入新特性。class 文件格式一旦發(fā)生變化,版本號(hào)也會(huì)隨之變化。對(duì)于 JVM 來(lái)說(shuō),版本號(hào)確定了特定的 class 文件格式,通常只有給定主版本號(hào)和一系列次版本號(hào)后,JVM 才能夠讀取 class 文件。如果 class 文件的版本號(hào)超出了 JVM 所能處理的有效范圍,JVM 將不會(huì)處理該 class 文件。
在 Sun 的 JDK 1.0.2 發(fā)布版中,JVM 實(shí)現(xiàn)支持從 45.0 到 45.3 的 class 文件格式。在所有 JDK 1.1 發(fā)布版中的 JVM 都能夠支持版本從 45.0 到 45.65535 的 class 文件格式。在 Sun 的 1.2 版本的 SDK 中,JVM 能夠支持從版本 45.0 到46.0 的 class 文件格式。
1.0 或 1.2 版本的編譯器能夠產(chǎn)生版本號(hào)為 45.3 的 class 文件。在 Sun 的 1.2 版本 SDK 中,Javac 編譯器默認(rèn)產(chǎn)生版本號(hào)為 45.3 的 class 文件。但如果在 javac 命令行中指定了 -target 1.2 標(biāo)志,1.2 版本的編譯器將產(chǎn)生版本號(hào)為 46.0 的 class 文件。1.0 或 1.1 版本的 JVM 上不能運(yùn)行使用-target 1.2 標(biāo)志所產(chǎn)生的 class 文件。
JVM 實(shí)現(xiàn)的 第二版中修改了對(duì) class 文件主版本號(hào)和次版本號(hào)的解釋。對(duì)于第二版而言,class 文件的主版本號(hào)與 Java 平臺(tái)主發(fā)布版的版本號(hào)保持一致(例如:在 Java 2 平臺(tái)發(fā)布版上,主版本號(hào)從 45 升至 46),次版本號(hào)與特定主平臺(tái)發(fā)布版的各個(gè)發(fā)布版相關(guān)。因此,盡管不同的 class 文件格式可以由不同的版本號(hào)表示,但版本號(hào)不一樣并不代表 class 文件格式不同。版本號(hào)不同的原因可能只是因?yàn)?class 文件由不同發(fā)布版本的 java 平臺(tái)產(chǎn)生,可能 class 文件的格式并沒(méi)有改變。
上面三段節(jié)選自《深入 Java 虛擬機(jī)》,啰嗦一堆,JDK 1.2 開(kāi)啟了 Java 2 的時(shí)代,但那個(gè)年代仍然離我們很遠(yuǎn),我們當(dāng)中很多少直接跳在 JDK 1.4 上的,我也差不多,只是項(xiàng)目要求不得不在一段時(shí)間里委屈在 JDK 1.3 上。不過(guò)大致我們可以得到的信息就是每個(gè)版本的 JDK 編譯器編譯出的 class 文件中都帶有一個(gè)版本號(hào),不同的 JVM 能接受一個(gè)范圍 class 版本號(hào),超出范圍則要出錯(cuò)。不過(guò)一般都是能向后兼容的,知道 Sun 在做 Solaris 的一句口號(hào)嗎?保持對(duì)先前版本的 100% 二進(jìn)制兼容性,這也是對(duì)客戶的投資保護(hù)。
四:其他確定 class 的 major.minor version 辦法
1)Eclipse 中查看
Eclipse 3.3 加入的新特征,當(dāng)某個(gè)類沒(méi)有關(guān)聯(lián)到源代碼,打開(kāi)它會(huì)顯示比較詳細(xì)的類信息,當(dāng)然還未到源碼級(jí)別了,看下圖是打開(kāi) 2.0 spring.jar 中 ClasspathXmlApplicationContext.class 顯示的信息
2)命令 javap -verbose
對(duì)于編譯出的 class 文件用 javap -verbose 能顯示出類的 major.minor 版本,見(jiàn)下圖:
3) MANIFEST 文件
把 class 打成的 JAR 包中都會(huì)有文件 META-INF\MANIFEST,這個(gè)文件一般會(huì)有編譯器的信息,下面列幾個(gè)包的 META-INF\MANIFEST 文件內(nèi)容大家看看
1
2
3
4
5
6
7
|
·Velocity-1.5.jar 的 META-INFO\MANIFEST 部份內(nèi)容 Manifest-Version: 1.0 Ant-Version: Apache Ant 1.7.0 Created-By: Apache Ant Package: org.apache.velocity Build-Jdk: 1.4.2_08 Extension-Name: velocity |
我們看到是用 ant 打包,構(gòu)建用的JDK是 1.4.2_08,用 1.4 編譯的類在 1.4 JVM 中當(dāng)然能運(yùn)行。如果那人用 1.5 的 JDK 來(lái)編譯,然后用 JDK 1.4+ANT 來(lái)打包就太無(wú)聊了。
·2.0 spring.jar 的 META-INFO\MANIFEST 部份內(nèi)容
1
2
3
4
|
Manifest-Version: 1.0 Ant-Version: Apache Ant 1.6.5 Created-By: 1.5.0_08-b03 (Sun Microsystems Inc.) Implementation-Title: Spring Framework |
這下要注意啦,它是用的 JDK 1.5 來(lái)編譯的,那么它是否帶了 -target 1.4 或 -target 1.3 來(lái)編譯的呢?確實(shí)是的,可以查看類的二進(jìn)制文件,這是最保險(xiǎn)的。所在 spring-2.0.jar 也可以在 1.4 JVM 中加載執(zhí)行。
·自已一個(gè)項(xiàng)目中用 ant 打的 jar 包的 META-INFO\MANIFEST
1
2
3
|
Manifest-Version: 1.0 Ant-Version: Apache Ant 1.7.0 Created-By: 1.4.2-b28 (Sun Microsystems Inc.) |
用的是 JDK 1.4 構(gòu)建打包的。
第一第二種辦法能明確知道 major.minor version,而第三種方法應(yīng)該也沒(méi)問(wèn)題,但是碰到變態(tài)構(gòu)建就難說(shuō)了,比如誰(shuí)把那個(gè) META-INFO\MANIFEST 打包后換了也未可知。直接查看類的二進(jìn)制文件的方法可以萬(wàn)分保證,準(zhǔn)確無(wú)誤,就是工具篡改我也認(rèn)了。
五:編譯器比較及癥節(jié)之所在
現(xiàn)在不妨從 JDK 1.1 到 JDK 1.7 編譯器編譯出的 class 的默認(rèn) minor.major version 吧。(又走到 Sun 的網(wǎng)站上翻騰出我從來(lái)都沒(méi)用過(guò)的古董來(lái))
JDK 編譯器版本 | target 參數(shù) | 十六進(jìn)制 minor.major | 十進(jìn)制 minor.major |
jdk1.1.8 | 不能帶 target 參數(shù) | 00 03 00 2D | 45.3 |
jdk1.2.2 | 不帶(默認(rèn)為 -target 1.1) | 00 03 00 2D | 45.3 |
jdk1.2.2 | -target 1.2 | 00 00 00 2E | 46.0 |
jdk1.3.1_19 | 不帶(默認(rèn)為 -target 1.1) | 00 03 00 2D | 45.3 |
jdk1.3.1_19 | -target 1.3 | 00 00 00 2F | 47.0 |
j2sdk1.4.2_10 | 不帶(默認(rèn)為 -target 1.2) | 00 00 00 2E | 46.0 |
j2sdk1.4.2_10 | -target 1.4 | 00 00 00 30 | 48.0 |
jdk1.5.0_11 | 不帶(默認(rèn)為 -target 1.5) | 00 00 00 31 | 49.0 |
jdk1.5.0_11 | -target 1.4 -source 1.4 | 00 00 00 30 | 48.0 |
jdk1.6.0_01 | 不帶(默認(rèn)為 -target 1.6) | 00 00 00 32 | 50.0 |
jdk1.6.0_01 | -target 1.5 | 00 00 00 31 | 49.0 |
jdk1.6.0_01 | -target 1.4 -source 1.4 | 00 00 00 30 | 48.0 |
jdk1.7.0 | 不帶(默認(rèn)為 -target 1.6) | 00 00 00 32 | 50.0 |
jdk1.7.0 | -target 1.7 | 00 00 00 33 | 51.0 |
jdk1.7.0 | -target 1.4 -source 1.4 | 00 00 00 30 | 48.0 |
Apache Harmony 5.0M3 | 不帶(默認(rèn)為 -target 1.2) | 00 00 00 2E | 46.0 |
Apache Harmony 5.0M3 | -target 1.4 | 00 00 00 30 | 48.0 |
上面比較是 Windows 平臺(tái)下的 JDK 編譯器的情況,我們可以此作些總結(jié):
1) -target 1.1 時(shí) 有次版本號(hào),target 為 1.2 及以后都只用主版本號(hào)了,次版本號(hào)為 0
2) 從 1.1 到 1.4 語(yǔ)言差異比較小,所以 1.2 到 1.4 默認(rèn)的 target 都不是自身相對(duì)應(yīng)版本
3) 1.5 語(yǔ)法變動(dòng)很大,所以直接默認(rèn) target 就是 1.5。也因?yàn)槿绱擞?1.5 的 JDK 要生成目標(biāo)為 1.4 的代碼,光有 -target 1.4 不夠,必須同時(shí)帶上 -source 1.4,指定源碼的兼容性,1.6/1.7 JDk 生成目標(biāo)為 1.4 的代碼也如此。
4) 1.6 編譯器顯得較為激進(jìn),默認(rèn)參數(shù)就為 -target 1.6。因?yàn)?1.6 和 1.5 的語(yǔ)法無(wú)差異,所以用 -target 1.5 時(shí)無(wú)需跟著 -source 1.5。
5) 注意 1.7 編譯的默認(rèn) target 為 1.6
6) 其他第三方的 JDK 生成的 Class 文件格式版本號(hào)同對(duì)應(yīng) Sun 版本 JDK
7) 最后一點(diǎn)最重要的,某個(gè)版本的 JVM 能接受 class 文件的最大主版本號(hào)不能超過(guò)對(duì)應(yīng) JDK 帶相應(yīng) target 參數(shù)編譯出來(lái)的 class 文件的版本號(hào)。
上面那句話有點(diǎn)長(zhǎng),一口氣讀過(guò)去不是很好理解,舉個(gè)例子:1.4 的 JVM 能接受最大的 class 文件的主版本號(hào)不能超過(guò)用 1.4 JDK 帶參數(shù) -target 1.4 時(shí)編譯出的 class 文件的主版本號(hào),也就是 48。
因?yàn)?1.5 JDK 編譯時(shí)默認(rèn) target 為 1.5,出來(lái)的字節(jié)碼 major.minor version 是 49.0,所以 1.4 的 JVM 是無(wú)法接受的,只有拋出錯(cuò)誤。
那么又為什么從 1.1 到 1.2、從 1.2 到 1.3 或者從 1.3 到 1.4 的 JDK 升級(jí)不會(huì)發(fā)生 Unsupported major.minor version 的錯(cuò)誤呢,那是因?yàn)?1.2/1.3/1.4 都保持了很好的二進(jìn)制兼容性,看看 1.2/1.3/1.4 的默認(rèn) target 分別為 1.1/1.1/1.2 就知道了,也就是默認(rèn)情況下1.4 JDK 編譯出的 class 文件在 JVM 1.2 下都能加載執(zhí)行,何況于 JVM 1.3 呢?(當(dāng)然要去除使用了新版本擴(kuò)充的 API 的因素)
六:找到問(wèn)題解決的方法
那么現(xiàn)在如果碰到這種問(wèn)題該知道如何解決了吧,還會(huì)像我所見(jiàn)到有些兄弟那樣,去找個(gè) 1.4 的 JDK 下載安裝,然后用其重新編譯所有的代碼嗎?其實(shí)大可不必如此費(fèi)神,我們一定還記得 javac 還有個(gè) -target 參數(shù),對(duì)啦,可以繼續(xù)使用 1.5 JDK,編譯時(shí)帶上參數(shù) -target 1.4 -source 1.4 就 OK 啦,不過(guò)你一定要對(duì)哪些 API 是 1.5 JDK 加入進(jìn)來(lái)的了如指掌,不能你的 class 文件拿到 JVM 1.4 下就會(huì) method not found。目標(biāo) JVM 是 1.3 的話,編譯選項(xiàng)就用 -target 1.3 -source 1.3 了。
相應(yīng)的如果使用 ant ,它的 javac 任務(wù)也可對(duì)應(yīng)的選擇 target 和 source
1
|
<javac target= "1.4" source= "1.4" ............................/> |
如果是在開(kāi)發(fā)中,可以肯定的是現(xiàn)在真正算得上是 JAVA IDE 對(duì)于工程也都有編譯選項(xiàng)設(shè)置目標(biāo)代碼的。例如 Eclipse 的項(xiàng)目屬性中的 Java Compiler 設(shè)置,如圖
自已設(shè)定編譯選項(xiàng),你會(huì)看到選擇不同的 compiler compliance level 是,Generated class files compatibility 和 Source compatibility 也在變,你也可以手動(dòng)調(diào)整那兩項(xiàng),手動(dòng)設(shè)置后你就不用很在乎用的什么版本的編譯器了,只要求他生成我們希望的字節(jié)碼就行了,再引申一下就是即使源代碼是用 VB 寫(xiě)的,只要能編譯成 JVM 能執(zhí)行的字節(jié)碼都不打緊。在其他的 IDE 也能找到相應(yīng)的設(shè)置對(duì)話框的。
其他時(shí)候,你一定要知道當(dāng)前的 JVM 是什么版本,能接受的字節(jié)碼主版本號(hào)是多少(可對(duì)照前面那個(gè)表)。獲息當(dāng)前 JVM 版本有兩種途徑:
第一:如果你是直接用 java 命令在控制臺(tái)執(zhí)行程序,可以用 java -version 查看當(dāng)前的 JVM 版本,然后確定能接受的 class 文件版本
第二:如果是在容器中執(zhí)行,而不能明確知道會(huì)使用哪個(gè) JVM,那么可以在容器中執(zhí)行的程序中加入代碼 System.getProperty("java.runtime.version"); 或 System.getProperty("java.class.version"),獲得 JVM 版本和能接受的 class 的版本號(hào)。
最后一絕招,如果你不想針對(duì)低版本的 JVM 用 target 參數(shù)重新編譯所有代碼;如果你仍然想繼續(xù)在代碼中用新的 API 的話;更有甚者,你還用了 JDK 1.5 的新特性,譬如泛型、自動(dòng)拆裝箱、枚舉等的話,那你用 -target 1.4 -source 1.4 就沒(méi)法編譯通過(guò),不得不重新整理代碼。那么告訴你最后一招,不需要再?gòu)脑创a著手,直接轉(zhuǎn)換你所正常編譯出的字節(jié)碼,繼續(xù)享用那些新的特性,新的 API,那就是:請(qǐng)參考之前的一篇日志:Retrotranslator讓你用JDK1.5的特性寫(xiě)出的代碼能在JVM1.4中運(yùn)行 ,我就是這么用的,做好測(cè)試就不會(huì)有問(wèn)題的。
七:再議一個(gè)實(shí)際發(fā)生的相關(guān)問(wèn)題
這是一個(gè)因?yàn)榭截?Tomcat 而產(chǎn)生的 Unsupported major.minor version 49.0 錯(cuò)誤。情景是:我本地安裝的是 JDK 1.5,然后在網(wǎng)上找了一個(gè) EXE 的 Tomcat 安裝文件安裝了并且可用。后來(lái)同事要一個(gè) Tomcat,不想下載或安裝,于是根據(jù)我以往的經(jīng)驗(yàn)是把我的 Tomcat 整個(gè)目錄拷給他應(yīng)該就行了,結(jié)果是拿到他那里瀏覽 jsp 文件都出現(xiàn) Unsupported major.minor version 49.0 錯(cuò)誤,可以確定的是他安裝的是 1.4 的 JDK,但我還是有些納悶,先前對(duì)這個(gè)問(wèn)題還頗有信心的我傻眼了。慣性思維是編譯好的 class 文件拿到低版本的 JVM 會(huì)出現(xiàn)如是異常,可現(xiàn)并沒(méi)有用已 JDK 1.5 編譯好的類要執(zhí)行啊。
后來(lái)仔細(xì)看異常信息,終于發(fā)現(xiàn)了 %TOMCAT_HOME%\common\lib\tools.jar 這一眉目,因?yàn)?jsp 文件需要依賴它來(lái)編譯,打來(lái)這個(gè) tools.jar 中的一個(gè) class 文件來(lái)看看,49.0,很快我就明白原來(lái)這個(gè)文件是在我的機(jī)器上安裝 Tomcat 時(shí)由 Tomcat 安裝程序從 %JDK1.5%\lib 目錄拷到 Tomcat 的 lib 目錄去的,造成在同事機(jī)器上編譯 JSP 時(shí)是 1.4 的 JVM 配搭著 49.0 的 tools.jar,那能不出錯(cuò),于是找來(lái) 1.4 JDK 的 tools.jar 替換了 Tomcat 的就 OK 啦。
八:小結(jié)
其實(shí)理解 major.minor 就像是我們可以這么想像,同樣是微軟件的程序,32 位的應(yīng)用程序不能拿到 16 位系統(tǒng)中執(zhí)行那樣。
如果我們發(fā)布前了解到目標(biāo) JVM 版本,知道怎么從 java class 文件中看出 major.minor 版本來(lái),就不用等到服務(wù)器報(bào)出異常才著手去解決,也就能預(yù)知到可能發(fā)生的問(wèn)題。
其他時(shí)候遇到這個(gè)問(wèn)題應(yīng)具體解決,總之問(wèn)題的根由是低版本的 JVM 無(wú)法加載高版本的 class 文件造成的,找到高版本的 class 文件處理一下就行了
下面是關(guān)于Tomcat 運(yùn)行第一個(gè)servlet時(shí)發(fā)生Unsupported major.minor version 52.0錯(cuò)誤的解決辦法
使用Tomcat運(yùn)行第一個(gè)servlet程序是發(fā)生如圖錯(cuò)誤:
查找資料后發(fā)現(xiàn)是由于使用高版本的jdk編譯的項(xiàng)目運(yùn)行在低版本的jre上,決解辦法是編譯的jdk和運(yùn)行的jre版本一致。
查看Tomcat使用的jre確實(shí)和編譯時(shí)使用的不一致(當(dāng)初安裝了兩個(gè)jdk);
后面把Tomcat的jre改為1.8就可正常運(yùn)行:
no art, however minor, demands less than total dedication if you want to excel in it.