摘自《Java高併發編程詳解-多線程架構與設計》第九章 p144-p157
Java語言規範
文章目錄
重點:
- 連接-準備階段爲靜態變量賦初值,初始化階段爲靜態變量賦代碼值
- 引起初始化階段的6種情況(主動引用),靜態變量/方法,new,反射,子類引起父類。以及被動引用,如靜態常量。其值opy到被引用的類。
- 類加載的三個階段
- 靜態代碼塊可以對後面的靜態變量賦值,但不能訪問。
- clinit線程安全
1.類加載的三個過程-簡述
-
加載階段
查找並加載class文件
loadClass from somewhere -> getClassBytes and defineClass->return Class
常見異常:ClassNotFoundException,NoClassDefFoundError -
連接階段
a. 驗證
確保類文件正確性。如class版本,魔數 CAFEBABE
b. 準備-賦初始值
爲類的靜態變量 分配內存,初始化【默認值】。
c. 解析
將類中符號引用轉爲直接引用。
如引用了其他類, 則其他類先要經歷類的所有加載過程。 -
初始化階段
<clinit>()
-賦代碼值
class initailze爲類的靜態變量賦予正確的值(代碼編寫時指定的值)
2. 類的主動使用和被動使用
JVM虛擬機規定,類和接口被java程序首次主動使用纔會對其進行初始化。
主動使用的場景-new,反射,使用靜態部分,子導致父初始化
- new
- 訪問類的靜態變量
- 訪問類的靜態方法
- 對類進行反射操作 。 如 Class.forName(“xx”); 參考java.sql.driver初始化驅動時調用DriverManager註冊driver。
- 初始化子類導致父類的初始化
同理, 調用子類的靜態變量會導致父類的初始化。
6. 啓動類:執行main鎖在的類會導致該類的初始化
除了上述6種,其他的動成爲被動使用,不會導致類的【加載】和【初始化】。
被動使用
-
引用類的靜態常量
ps:靜態常量會在編譯期間被計算出來copy到其他類,可使用jstack查看class文件 -
聲明數組
3. 類的加載過程詳解
例子
3.1 類的加載階段
關鍵:1.得到class對應的二進制流(不限方式)2.defineClass
類的加載最終產物是堆內存中的class對象,對於同一個classLoader,不管加載多少次,對應到堆內存中的class是同一個(from cache)。
所以可以使用不同的classLoader加載同名的不同類
虛擬機規範規定類的加載是通過全限定名(包名+類名)來獲取二進制數據流。
調用loadClass->findClass->getClassBytes+defineClass
常見的是class二進制文件形式,但可以是以下:
- 運行時動態生成,如ASM ,jdk動態代理,CGLIB
- 網絡獲取
- 讀取zip獲得類的二進制字節流,如war,jar。
war,jar使用的是和zip一樣的壓縮算法。 - 將類的二進制文件儲存在數據庫的BLOB字段類型中。
- 運行時生成class文件,並動態加載,如Thrift/AVRO可以在運行時將schema文件生成對應的class文件再加載。
3.2 類的連接階段
驗證
文件格式:確保類文件正確性。如class版本,魔數 CAFEBABE,MD5比對(class末尾)等。
元數據:確保class符合jvm規範。語義驗證。
字節碼:…
符號引用:…
準備
爲類的靜態變量 分配內存,初始化默認值。
如 static int a = 10; 在【準備階段】a是0而不是10。
如final static int b = 10; 注意,final static變量是【被動引用】,在編譯期間即直接賦予了10;
解析
將類中符號引用轉爲直接引用。
在常量池中尋找類、接口、字段、方法的符號引用,將其替換成直接引用。
初始化
()保證順序,對後面的靜態變量只能賦值不能訪問
類加載過程實例剖析
發佈於2019年7月14日 20:24:54