背景
由于最近Oracle宣布JDK8的新收費政策之后,公司決定遷移java環(huán)境到OpenJDK上面。在完成了遷移之后,發(fā)現(xiàn)了有兩個接口拋出了NoClassDefFoundError。調(diào)查之后發(fā)現(xiàn)是openJDK里面缺少了sun.lwawt.macosx.LWCToolkit這個包導(dǎo)致的問題。
借此機會,記錄一下這個問題,也順便回顧了一下JVM加載和初始化class的過程,同時感慨一下果然冒煙測試和UT跑過之后還是要放心很多 :)
具體問題 以及 解決方案
總的來說,還是比較容易定位到問題的,因為stack trace還是滿明顯的:
Caused by: java.lang.NoClassDefFoundError: Could not initialize class sun.lwawt.macosx.LWCToolkit
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at java.awt.Toolkit$2.run(Toolkit.java:860)
at java.awt.Toolkit$2.run(Toolkit.java:855)
at java.security.AccessController.doPrivileged(Native Method)
at java.awt.Toolkit.getDefaultToolkit(Toolkit.java:854)
at java.awt.Image.getScaledInstance(Image.java:178)
具體代碼拋異常的地方:
public static synchronized Toolkit getDefaultToolkit() {
if (toolkit == null) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
Class<?> cls = null;
String nm = System.getProperty("awt.toolkit");
try {
// 兇手在這里~
cls = Class.forName(nm);
} catch (ClassNotFoundException e) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
if (cl != null) {
try {
cls = cl.loadClass(nm);
} catch (final ClassNotFoundException ignored) {
throw new AWTError("Toolkit not found: " + nm);
}
}
}
try {
if (cls != null) {
toolkit = (Toolkit)cls.newInstance();
if (GraphicsEnvironment.isHeadless()) {
toolkit = new HeadlessToolkit(toolkit);
}
}
} catch (final InstantiationException ignored) {
throw new AWTError("Could not instantiate Toolkit: " + nm);
} catch (final IllegalAccessException ignored) {
throw new AWTError("Could not access Toolkit: " + nm);
}
return null;
}
});
loadAssistiveTechnologies();
}
return toolkit;
}
可以看到這里主要通過Class.forName(str)來動態(tài)加載的Class,所以在編譯階段沒有拋出相關(guān)的異常,而是在運行到這部分之后才發(fā)現(xiàn)這個問題。
這里想到之前想通過java -verbose方式來精簡JDK的時候,也是很容易因為這些動態(tài)加載的情況,造成包的誤刪除。
知道原因之后,解決方案就比較容易了,一個是更換這里的處理方式,使用其它的方式;當然有同事還提出了一個更為亮眼的解決方式:把sun jdk下面的jar包直接拷貝過來……(是的,我也被這個思路震驚了,哈哈哈)
Java的類加載實例化步驟
過程中,順便回顧了一下Java對類的加載和實例化步驟(注意這里是開始順序,并非一定是結(jié)束順序相關(guān)聯(lián)),這里也記錄一下,以免后面又搞混了。
- 加載: 通過加載器將二進制文件讀入到JVM中(這里主要涉及到classloader的雙親委托機制)
- 驗證:
2.1 驗證文件格式:例如CAFEBABE的標識(是的,我第一次看《深入JVM虛擬機》的時候?qū)@個類型字也震驚了,果然程序員還是浪漫,不知道算不算彩蛋,哈哈哈)
2.2 驗證元數(shù)據(jù)
2.3 驗證字節(jié)碼
2.4 驗證符號引用 - 準備(option):
3.1 為靜態(tài)變量分配內(nèi)存
3.2 初始化靜態(tài)變量為默認值(注意這里是默認的0值,而不是我們賦予的初始值) - 解析: 將符號引用轉(zhuǎn)化為直接引用
- 初始化:對類和類中的變量進行初始化(賦予初始值等)
關(guān)于初始化的時機,也記錄一下:
- 實例化對象,如new
- 訪問靜態(tài)變量
- 訪問靜態(tài)方法
- 反射
- 初始化子類
- 啟動時被標記為啟動的類(比如main的入口類)
結(jié)語
在處理過程中,還發(fā)現(xiàn)OpenJDK對JPG圖片的處理上,和sun JDK也有一些區(qū)別,查了一下資料,感覺主要是在alpha通道的處理上有不一樣的地方。
這么一看,感覺sun對這部分的處理還有點高級,居然能支持alpha通道,也不知道是不是以后可以玩帶透明度的JPG了 :P
參考如下:
