為什么要使用類加載器?
java語(yǔ)言里,類加載都是在程序運(yùn)行期間完成的,這種策略雖然會(huì)令類加載時(shí)稍微增加一些性能開銷,但是會(huì)給java應(yīng)用程序提供高度的靈活性。例如:
1.編寫一個(gè)面向接口的應(yīng)用程序,可能等到運(yùn)行時(shí)再指定其實(shí)現(xiàn)的子類;
2.用戶可以自定義一個(gè)類加載器,讓程序在運(yùn)行時(shí)從網(wǎng)絡(luò)或其他地方加載一個(gè)二進(jìn)制流作為程序代碼的一部分;(這個(gè)是android插件化,動(dòng)態(tài)安裝更新apk的基礎(chǔ))
為什么研究類加載全過程?
- 有助于連接jvm運(yùn)行過程
- 更深入了解java動(dòng)態(tài)性(解熱部署,動(dòng)態(tài)加載),提高程序的靈活性
類加載機(jī)制
jvm把class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、解析和初始化,最終形成jvm可以直接使用的java類型的全過程。
加載
將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)中的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),在堆中生成一個(gè)代表這個(gè)類的java.lang.class
對(duì)象,作為方法區(qū)類數(shù)據(jù)的訪問入口,這個(gè)過程需要類加載器參與。
鏈接
將java類的二進(jìn)制代碼合并到j(luò)vm的運(yùn)行狀態(tài)之中的過程
- 驗(yàn)證: 確保加載的類信息符合jvm規(guī)范,沒有安全方面的問題
- 準(zhǔn)備: 正式為類變量(static變量)分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法去中進(jìn)行分配
- 解析: 虛擬機(jī)常量池的符號(hào)引用替換為字節(jié)引用過程
初始化
-
初始化階段是執(zhí)行類構(gòu)造器
<clinit>()
方法的過程。類構(gòu)造器<clinit>()
方法是由編譯器自動(dòng)收藏類中的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static塊)中的語(yǔ)句合并產(chǎn)生 - 當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化
-
虛擬機(jī)會(huì)保證一個(gè)類的
<clinit>()
方法在多線程環(huán)境中被正確加鎖和同步 - 當(dāng)范圍一個(gè)java類的靜態(tài)域時(shí),只有真正聲名這個(gè)域的類才會(huì)被初始化
例1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class demo01 { public static void main(string[] args) { a a = new a(); system.out.println(a.width); } } class a{ public static int width= 100 ; //靜態(tài)變量,靜態(tài)域 field static { system.out.println( "靜態(tài)初始化類a" ); width = 300 ; } public a() { system.out.println( "創(chuàng)建a類的對(duì)象" ); } } |
分析:
說(shuō)明:
內(nèi)存中存在棧、堆(放創(chuàng)建好的對(duì)象)、方法區(qū)(實(shí)際也是一種特殊堆)
1、jvm加載demo01時(shí)候,首先在方法區(qū)中形成demo01類對(duì)應(yīng)靜態(tài)數(shù)據(jù)(類變量、類方法、代碼…),同時(shí)在堆里面也會(huì)形成java.lang.class
對(duì)象(反射對(duì)象),代表demo01類,通過對(duì)象可以訪問到類二進(jìn)制結(jié)構(gòu)。然后加載變量a類信息,同時(shí)也會(huì)在堆里面形成a對(duì)象,代表a類。
2、main方法執(zhí)行時(shí)會(huì)在棧里面形成main方法棧幀,一個(gè)方法對(duì)應(yīng)一個(gè)棧幀。如果main方法調(diào)用了別的方法,會(huì)在棧里面挨個(gè)往里壓,main方法里面有個(gè)局部變量a類型的a,一開始a值為null,通過new調(diào)用類a的構(gòu)造器,棧里面生成a()方法同時(shí)堆里面生成a對(duì)象,然后把a(bǔ)對(duì)象地址付給棧中的a,此時(shí)a擁有a對(duì)象地址。
3、當(dāng)調(diào)用a.width時(shí),調(diào)用方法區(qū)數(shù)據(jù)。
當(dāng)類被引用的加載,類只會(huì)加載一次
類的主動(dòng)引用(一定會(huì)發(fā)生類的初始化)
- new一個(gè)類的對(duì)象
- 調(diào)用類的靜態(tài)成員(除了final常量)和靜態(tài)方法
-
使用
java.lang.reflect
包的方法對(duì)類進(jìn)行反射調(diào)用 - 當(dāng)虛擬機(jī)啟動(dòng),java demo01,則一定會(huì)初始化demo01類,說(shuō)白了就是先啟動(dòng)main方法所在的類
- 當(dāng)初始化一個(gè)類,如果其父類沒有被初始化,則先初始化它父類
類的被動(dòng)引用(不會(huì)發(fā)生類的初始化)
- 當(dāng)訪問一個(gè)靜態(tài)域時(shí),只有真正聲名這個(gè)域的類才會(huì)被初始化
- 通過子類引用父類的靜態(tài)變量,不會(huì)導(dǎo)致子類初始化
- 通過數(shù)組定義類的引用,不會(huì)觸發(fā)此類初始化
- 引用常量不會(huì)觸發(fā)此類的初始化(常量在編譯階段就存入調(diào)用類的常量池中了)
例2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
public class demo01 { static { system.out.println( "靜態(tài)初始化demo01" ); } public static void main(string[] args) throws exception { system.out.println( "demo01的main方法!" ); system.out.println(system.getproperty( "java.class.path" )); //主動(dòng)引用 // new a(); // system.out.println(a.width); // class.forname("com.sinosoft.test.a"); //被動(dòng)引用 // system.out.println(a.max); // a[] as = new a[10]; system.out.println(b.width); //b類不會(huì)被加載 } } class b extends a { static { system.out.println( "靜態(tài)初始化b" ); } } class a extends a_father { public static int width= 100 ; //靜態(tài)變量,靜態(tài)域 field public static final int max= 100 ; static { system.out.println( "靜態(tài)初始化類a" ); width= 300 ; } public a(){ system.out.println( "創(chuàng)建a類的對(duì)象" ); } } class a_father extends object { static { system.out.println( "靜態(tài)初始化a_father" ); } } |
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)服務(wù)器之家的支持。
原文鏈接:https://juejin.im/post/5addb276f265da0b9c103e42