背景
之前在集成第三方即時通信系統-融雲的時候,我直接clone它的服務端源碼,然后導入我的項目,我在測試它連接融雲服務器案例時,發現一直不成功,始終報一個 ExceptionInInitializerError 的異常。后來通過網上查資料才發現,這個異常是靜態變量初始化時出現異常時,JVM會拋出java.lang.ExceptionInInitializerError的異常。由此,我對這個異常做了進一步探究。
拋出ExceptionInInitializerError異常的原因
因為這個異常時在靜態變量初始化發生異常時拋出的,所以首先我們了解一下靜態變量初始化的問題。
靜態變量初始化
提到靜態變量初始化,又不得不提JVM的類加載機制,把描述類的數據從class文件加載到內存,並對數據進行校驗,轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。
類加載的生命周期包括:加載,驗證,准備,解析,初始化,使用和卸載這7個階段。靜態變量的初始化相關操作主要在准備和初始化階段。
准備階段
對於我們的靜態變量來說,首先在准備階段進行類變量內存的分配以及設置類變量初始值,(類變量是指被static修飾的變量),例如:
public static int value = 123. //這時初始值為value = 0;
初始化階段
初始化階段是執行類構造器clinit()方法的過程:
clinit方法是有編譯器自動收集類中的所有類變量(類變量就是靜態變量)的賦值動作和靜態語句塊(static{})塊中的語句合並產生的,編譯器收集順序是 由語句在源文件中出現的順序所決定的,靜態語句塊只能訪問到定義在靜態語句塊之前的變量。
clinit()方法不需要顯式調用父類構造器,虛擬機會保證在子類clinit()方法執行之前,父類的clinit()方法已經執行完畢。所以虛擬機第一個執行的肯定是java.lang.object.
由於父類的clinit()方法先執行,也就意味着父類中定義的靜態語句塊要優於子類的變量賦值操作。
如果一個類中沒有靜態語句塊和堆變量賦值語句,編譯器可以不為這個類生成clinit()方法。
所以由以上可以看出類變量的初始化順序由它們在源文件中出現的順序決定的,如果第一行聲明了一個靜態變量,它在第二行被使用,實際上它卻在第三行被初始化,這樣的情況就會出現變量初始化異常,具體異常就是NullPointerException異常
引起ExceptionInInitializerError異常的真凶
分析完了靜態變量初始化問題,再來看具體引起ExceptionInInitializerError異常產生的原因是什么。在沒碰到ExceptionInInitializerError異常的時候,我也不知道具體能引起它的原因有什么,所以我還是利用了搜索引擎Google了一下,發現任何異常都有可能引起ExceptionInInitializerError異常的發生,比如說:java.lang.ArrayIndexOutOfBound或者java.lang.NullPointerException。
再來具體看看我項目中的實際情況是什么:
我運行代碼發現,控制台打印出了空指針的錯誤
Exception in thread "main" java.lang.ExceptionInInitializerError at io.rong.util.CommonUtil.getCheckInfo(CommonUtil.java:340) at io.rong.util.CommonUtil.checkFiled(CommonUtil.java:73) at io.rong.methods.user.User.register(User.java:59) at io.rong.example.user.UserExample.main(UserExample.java:40) Caused by: java.lang.NullPointerException //空指針 at io.rong.util.JsonUtil.<clinit>(JsonUtil.java:17) ... 4 more
空指針的錯誤具體位置是在:
public class JsonUtil { private static final String JSONFILE = JsonUtil.class.getClassLoader().getResource("jsonsource").getPath()+"/";//這個地方報錯 ... }
即也就是在靜態變量初始化的時候拋出的空指針異常。那么為什么會拋出空指針呢。接下來就要說JsonUtil.class.getClassLoader().getResource("jsonsource")的問題
class.getClassLoader()解析問題
getClassLoader()獲取的是這個 類對象的加載器,只有Class類才有getClassLoader()方法,當一個類被虛擬機加載完畢過后,也就是上面所說的類加載機制,然后會創建一個Class類實例,用於虛擬機對類的管理,所以JsonUtil.class就是獲得它的Class類,。
而JsonUtil.class.getClassLoader().getResource("jsonsource")就是在JsonUtil當前位置查找“jsonsource"這個資源文件。這是一個相對路徑,實際上它返回的是一個URL.
public URL getResource(String name) { URL url; ...
然后getPath(),就是獲取這個URL的路徑地址。但是卻報出 java.lang.NullPointerException空指針異常。說明資源沒找到。想想第三方平台肯定提供的資源的,結果仔細一看,是我沒有把它SDK中的resources資源文件導入,所以找不到“jsonsource"資源,所以才報出空指針錯誤,從而報出ExceptionInInitializerError異常。這才找到了最終的真凶。
參考資料
《深入理解java虛擬機》