深入理解Java類加載機制,再也不用死記硬背了
談談「會」的三個層次
在《說透分佈式事務》中,我舉例里說明了會與會的差別。對一門語言的學習,這裡談談我理解的「會」的三個層次:
第一層:了解這門語言的語法、寫法,我把它叫做 hello world 級別;
第二層:了解這門語言的優劣勢以及它的生態,了解這門語言的能力範圍,我把它叫做 應用 級別;
第三層:了解這門語言的底層運行機制,這有利於對程序進行調優,以及當程序遇到了比較罕見的問題時能夠從根上分析解決它。我把它叫做 掌握 級別。
在簡歷上寫掌握某種語言的,一般面試官也會問一些很深入原理的問題,個人認為比較合理。自己作為一個15年一線Java開發,自認為有資格把掌握Java寫在簡歷上。今天,我就來聊聊我對雙親委派機制一些理解。
插個題外話,在《高並發下秒殺商品,你必須知道的9個細節》中,有朋友問配圖是用什麼畫的。這裡介紹一下自己的經驗:
1)思維導圖還是processon更加方便:
//www.processon.com/i/594d313ae4b08b003f2ec84a
2)流程圖還是draw.io,這個不推薦在線編輯,慢到懷疑人生。安裝版本也是免費的,官網可輕鬆下載。開頭圖的框圖效果是draw.io的框圖有個 Sketch 樣式。這個樣式很好看,但是不建議用於文獻等正式場合。正式場合的圖最好方方正正,不要太圓潤,粗細均勻。
3)生成曲線圖、柱狀圖這些,還是習慣用excel。
Java類加載機制
首先我們需要思考一件事,我們編寫的Java代碼,是如何在各種各樣的操作系統上運行起來的。
Java文件通過Javac編譯成class文件,這種中間碼被稱為位元組碼。然後由JVM加載位元組碼。這個過程就稱為類加載。
運行時,由解釋器將位元組碼解釋為一行行的機器碼來執行。在程序運行期間,即時編譯器會針對熱點代碼,將該部分位元組碼編譯成機器碼以獲取更高的執行效率。在整個運行時,解釋器和即時編譯器相互配合,使程序幾乎能達到和編譯型語言幾乎一樣的執行速度。這個部分交給專業的編譯器開發人員來做,咱們本篇不做深入講解。
到此上面那張圖就講完了,不要問我右上角那兩個表情是怎麼回事。就是發現編輯的時候竟然可以添加表情,覺得好玩就試試看。
類的生命周期
在詳細講解之前,我們明確一下類加載流程的目的。站在高處去看,就是把一份被javac編譯過的文件通過加載,生成某種形式的class文件的數據結構送進內存。程序可以調用這個數據結構來構造出Java對象。這個過程是在運行時進行的,也是Java動態拓展性的根基。
上面這張圖表現了類的整個生命周期。而類加載呢,只包含了加載、鏈接和初始化三個階段。加載只是類加載的第一個環節,兩者要注意區分。解析部分是靈活的,它可以在初始化環節之前或者之後進行,實現後期綁定。類加載的其他環節的順序是不可改變的。
加載
加載是一個讀取class文件,將其轉化為某種靜態數據結構而存儲在方法區內,並在堆中生成一個便於用戶調用的Java對象的過程。
這裡值得注意的是,這個Java文件不一定是本地文件,泛指各種來源的二進制流,比如網絡、數據庫或者比如動態代理技術這樣的即時生成的class文件。
驗證
驗證的步驟很多,上面的圖畫得不完全準確。對文件格式的校驗其實是發生在加載階段的。通過才能順利加載。順利加載並不代表JVM完全認可了這個類,還要進行語法和語義上的分析,保證這個類不會危害JVM,這是對元數據和位元組碼上的驗證。在解析階段,還會進行符號引用的驗證。隨着JVM版本的升高,驗證過程也在被不斷豐富。
準備
準備就是為靜態變量賦初始值,注意這裡的初始值是JVM默認初始值,是固定的,不是咱們寫代碼時的那個初始值。這裡有個比較容易混淆的概念。
Java內存規範定義了方法區這種抽象概念。主流的JVM實現如HotSpot在JDK8之前使用永久代這種在堆中開闢專門空間的實現方式,JDK8之後使用元空間這種直接內存取代。
HotSpot的實現:類的元信息、常量池、靜態變量等都存在在JDK8之前都存在在永久代這種方法區的具體實現中。JDK8之後,常量池、靜態變量被從方法區移除,轉移到了堆中。元信息這些依然保留在方法區,具體的存儲方式改成了元空間。
解析
解析是將符號引用替換為直接引用。
靜態解析
符號引用就是假如類A引用了類B,加載階段是靜態解析,這時候B還沒有被放到JVM內存中,這時候A引用的只是代表B的符號,這是符號引用。
直接引用就是類A在解析階段發現自己引用了B,如果這個時候B還沒被加載。就是直接觸發B的類加載,之後B的符號引用會被替換成實際地址。這被稱為直接引用。
動態解析
本文類的生命周期部分引出了後期綁定這個概念。後期綁定其實就是動態解析。如果代碼使用了多態。B是一個抽象類或者接口,A就不能知道究竟要用哪個來替換,只能等到實際發生調動時在進行實際地址的替換。這就是為什麼有的解析發生在初始化之後。
總結
類加載的過程今天就講這些。咱們來回顧一下類加載的五個階段。
從JVM的角度看,加載的讀取二進制流和初始化階段,是開放了主導權給用戶的。用戶可以使用動態代理等手段選擇是否這個階段進行加載。還可以使用多態的手段選擇是否在這個階段進行初始化。而剩下的所有部分都是JVM內部完成的。
此時你可以閉上眼睛回顧一下類加載的五個階段,是不是不用死記硬背也能瞭然於胸了。