問題記錄:更換OpenJDK后,拋出NoClassDefFoundError

背景

由于最近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)),這里也記錄一下,以免后面又搞混了。

  1. 加載: 通過加載器將二進制文件讀入到JVM中(這里主要涉及到classloader的雙親委托機制)
  2. 驗證:
    2.1 驗證文件格式:例如CAFEBABE的標識(是的,我第一次看《深入JVM虛擬機》的時候?qū)@個類型字也震驚了,果然程序員還是浪漫,不知道算不算彩蛋,哈哈哈)
    2.2 驗證元數(shù)據(jù)
    2.3 驗證字節(jié)碼
    2.4 驗證符號引用
  3. 準備(option):
    3.1 為靜態(tài)變量分配內(nèi)存
    3.2 初始化靜態(tài)變量為默認值(注意這里是默認的0值,而不是我們賦予的初始值)
  4. 解析: 將符號引用轉(zhuǎn)化為直接引用
  5. 初始化:對類和類中的變量進行初始化(賦予初始值等)

關(guān)于初始化的時機,也記錄一下:

  1. 實例化對象,如new
  2. 訪問靜態(tài)變量
  3. 訪問靜態(tài)方法
  4. 反射
  5. 初始化子類
  6. 啟動時被標記為啟動的類(比如main的入口類)

結(jié)語

在處理過程中,還發(fā)現(xiàn)OpenJDK對JPG圖片的處理上,和sun JDK也有一些區(qū)別,查了一下資料,感覺主要是在alpha通道的處理上有不一樣的地方。
這么一看,感覺sun對這部分的處理還有點高級,居然能支持alpha通道,也不知道是不是以后可以玩帶透明度的JPG了 :P

參考如下:


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容