概述
Java類(lèi)加載過(guò)程包括以下五個(gè)階段:
- 加載
- 驗(yàn)證
- 準(zhǔn)備
- 解析
- 初始化
驗(yàn)證、準(zhǔn)備和解析三個(gè)階段統(tǒng)稱(chēng)連接階段。
加載、驗(yàn)證、準(zhǔn)備和初始化這幾個(gè)階段的開(kāi)始順序是確定的,解析階段不一定,可能會(huì)在初始化之后才開(kāi)始,也因此使得Java支持動(dòng)態(tài)綁定。
詳細(xì)了解下各個(gè)階段具體的動(dòng)作。
加載
加載階段完成的是class文件的字節(jié)流載入虛擬機(jī),虛擬機(jī)在此階段需要完成以下三個(gè)任務(wù):
- 通過(guò)全限定類(lèi)名獲取類(lèi)的二進(jìn)制字節(jié)流(不管文件的來(lái)源,網(wǎng)絡(luò)也好,磁盤(pán)文件或壓縮包也罷,只要能讀入二進(jìn)制流就可以);
- 將讀取的字節(jié)流代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu);
- 在內(nèi)存中(并沒(méi)有明確規(guī)定是在堆中,HotSpot中Class對(duì)象雖然也是對(duì)象,但是卻是存儲(chǔ)在方法區(qū)中的)生成一個(gè)代表此類(lèi)的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類(lèi)的各種信息的入口。
在加載過(guò)程中,會(huì)用到類(lèi)加載器,類(lèi)加載器負(fù)責(zé)加載類(lèi)文件到虛擬機(jī)中,并返回Class對(duì)象。
需要注意的是,數(shù)組類(lèi)本身是不通過(guò)類(lèi)加載器加載的,它是由java虛擬機(jī)直接創(chuàng)建的。數(shù)組類(lèi)也是Class對(duì)象,是比較特殊的類(lèi):
package jdk.test.classLoader;
public class ArrayLoadTest {
public static void main(String[] args) {
Test[] test = new Test[10];
int[] i = { 0 };
System.out.println(test.getClass());
System.out.println(test.getClass().getClassLoader());
System.out.println(i.getClass());
System.out.println(i.getClass().getClassLoader());
}
}
// class [Ljdk.test.classLoader.Test;
// sun.misc.Launcher$AppClassLoader@73d16e93
// class [I
// null
從代碼執(zhí)行結(jié)果看,class [Ljdk.test.classLoader.Test;類(lèi)是由AppClassLoader加載的啊,int數(shù)組類(lèi)的加載器是null,那么這個(gè)null是不是BootStrapClassLoader呢?
一個(gè)數(shù)組類(lèi)的創(chuàng)建過(guò)程遵循以下原則:
- 數(shù)組元素類(lèi)型是引用類(lèi)型,如上面的test,會(huì)使用元素類(lèi)的類(lèi)加載器去加載這個(gè)Test類(lèi),但是數(shù)組會(huì)在這個(gè)類(lèi)加載器的類(lèi)名稱(chēng)空間上被標(biāo)識(shí)。
- 如果數(shù)組元素是基本類(lèi)型jvm會(huì)將此數(shù)組標(biāo)記為何引導(dǎo)類(lèi)加載器關(guān)聯(lián)。
可以使用java -verbose命令行選項(xiàng)查看類(lèi)加載的軌跡。
驗(yàn)證
驗(yàn)證階段主要對(duì)輸入的字節(jié)碼進(jìn)行檢查,確保其內(nèi)容符合規(guī)范。
實(shí)際上在加載期間就已經(jīng)開(kāi)始了驗(yàn)證過(guò)程,在讀入字節(jié)流之后需要經(jīng)過(guò)驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,且能被當(dāng)前虛擬機(jī)執(zhí)行,驗(yàn)證通過(guò)后才會(huì)存儲(chǔ)到方法區(qū)中,在之后的其他驗(yàn)證都是對(duì)方法區(qū)的數(shù)據(jù)進(jìn)行驗(yàn)證的,所以驗(yàn)證和加載時(shí)交叉的。
準(zhǔn)備
準(zhǔn)備階段的任務(wù)是給靜態(tài)變量分配內(nèi)存和設(shè)置初始值(零值),final static變量(實(shí)際上就是常量)會(huì)直接賦代碼中實(shí)際要賦的值,這些變量都將在方法區(qū)中分配內(nèi)存。
解析
解析階段是虛擬機(jī)將常量池中的符號(hào)引用替換成直接引用的過(guò)程。
通俗地說(shuō)就是把占位符換成真正的地址(內(nèi)存偏移等)
使用javap -verbose反編譯class文件可以看到常量池。
$ javac -verbose Test.java
[解析開(kāi)始時(shí)間 RegularFileObject[Test.java]]
[解析已完成, 用時(shí) 78 毫秒]
[源文件的搜索路徑: .,G:\Program Files\Java\jdk1.8.0_45\lib]
[類(lèi)文件的搜索路徑: G:\Program Files\Java\jdk1.8.0_45\jre\lib\resources.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\rt.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\sunrsasign.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\jsse.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\jce.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\charsets.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\jfr.jar,G:\Program Files\Java\jdk1.8.0_45\jre\classes,G:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar,G:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar,.,G:\Program Files\Java\jdk1.8.0_45\lib]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Object.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/String.class)]]
[正在檢查jdk.test.classLoader.Test]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/io/Serializable.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/AutoCloseable.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Byte.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Character.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Short.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Long.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Float.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Integer.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Double.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Boolean.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Void.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/System.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/io/PrintStream.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/io/FilterOutputStream.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/io/OutputStream.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Comparable.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/CharSequence.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Appendable.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/io/Closeable.class)]]
[正在加載ZipFileIndexFileObject[G:\Program Files\Java\jdk1.8.0_45\lib\ct.sym(META-INF/sym/rt.jar/java/io/Flushable.class)]]
[已寫(xiě)入RegularFileObject[Test.class]]
[共 1743 毫秒]
$ javap -verbose Test
警告: 二進(jìn)制文件Test包含jdk.test.classLoader.Test
Classfile /D:/workspace/Test/src/jdk/test/classLoader/Test.class
Last modified 2018-4-14; size 520 bytes
MD5 checksum 601c8f82d6b3a2ac4ce7c5b04e013d92
Compiled from "Test.java"
public class jdk.test.classLoader.Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #19.#20 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #21 // hello world
#4 = Methodref #22.#23 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #24 // jdk/test/classLoader/Test
#6 = Methodref #5.#18 // jdk/test/classLoader/Test."<init>":()V
#7 = Methodref #5.#25 // jdk/test/classLoader/Test.hello:()V
#8 = Class #26 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 hello
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 SourceFile
#17 = Utf8 Test.java
#18 = NameAndType #9:#10 // "<init>":()V
#19 = Class #27 // java/lang/System
#20 = NameAndType #28:#29 // out:Ljava/io/PrintStream;
#21 = Utf8 hello world
#22 = Class #30 // java/io/PrintStream
#23 = NameAndType #31:#32 // println:(Ljava/lang/String;)V
#24 = Utf8 jdk/test/classLoader/Test
#25 = NameAndType #13:#10 // hello:()V
#26 = Utf8 java/lang/Object
#27 = Utf8 java/lang/System
#28 = Utf8 out
#29 = Utf8 Ljava/io/PrintStream;
#30 = Utf8 java/io/PrintStream
#31 = Utf8 println
#32 = Utf8 (Ljava/lang/String;)V
{
public jdk.test.classLoader.Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 4: 0
line 5: 4
public void hello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hello world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 8: 0
line 9: 8
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #5 // class jdk/test/classLoader/Test
3: dup
4: invokespecial #6 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #7 // Method hello:()V
12: return
LineNumberTable:
line 12: 0
line 13: 8
line 14: 12
}
SourceFile: "Test.java"
初始化
初始化時(shí)類(lèi)加載的最后階段,初始化階段是執(zhí)行類(lèi)構(gòu)造器<clinit>()方法的過(guò)程。
初始化的執(zhí)行時(shí)機(jī),有5種情況會(huì)觸發(fā)初始化:
- 遇到new、getstatic、putstatic或invokestatic指令時(shí),如果相關(guān)的類(lèi)沒(méi)有初始化,會(huì)觸發(fā)初始化。場(chǎng)景有new創(chuàng)建對(duì)象,讀寫(xiě)類(lèi)的靜態(tài)變量或調(diào)用類(lèi)的靜態(tài)方法。注意如果是編譯時(shí)期加入常量池的靜態(tài)變量(final static 常量),那么這個(gè)靜態(tài)變量與定義它的類(lèi)已經(jīng)剝離了關(guān)系,這種調(diào)用不會(huì)觸發(fā)該類(lèi)的初始化。
例如:
public class InitOrder {
static {
System.out.println("initOrder init!");
}
public static void main(String[] args) {
System.out.println("before get final static var");
// Demo.A在編譯時(shí)加入了常量池,是共享的數(shù)據(jù),訪問(wèn)不會(huì)觸發(fā)Demo類(lèi)的初始化
int a = Demo.A;
System.out.println("get final static var end");
System.out.println("before get static var");
// 非常量的訪問(wèn) 會(huì)觸發(fā)初始化
int b = Demo.a;
System.out.println("get static var end");
}
}
class Demo {
static int a = 100;
final static int A = 1000;
static {
System.out.println("Demo init");
}
}
// 結(jié)果
// initOrder init!
// before get final static var
// get final static var end
// before get static var
// Demo init
// get static var end
- 使用Java反射機(jī)制的時(shí)候,如果類(lèi)沒(méi)有初始化,會(huì)觸發(fā)初始化。
- 初始化一個(gè)類(lèi)時(shí),會(huì)先初始化其父類(lèi)(如果父類(lèi)沒(méi)有初始化的話(huà))
- JVM啟動(dòng)時(shí),程序的入口類(lèi)會(huì)先初始化
- 使用JDK1.7的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,且這個(gè)句柄對(duì)應(yīng)的類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先進(jìn)行初始化。
有且僅有以上5種場(chǎng)景會(huì)觸發(fā)初始化。
<clinit>() <init>()
<clinit>()方法是由編譯器自動(dòng)收集類(lèi)中靜態(tài)變量的賦值語(yǔ)句以及靜態(tài)代碼塊中定義的語(yǔ)句合并產(chǎn)生的,且內(nèi)部的語(yǔ)句的順序是由定義的順序決定的,后面的語(yǔ)句可以訪問(wèn)前面定義的變量,反過(guò)來(lái)可以賦值,但是不能訪問(wèn)。
static{
i=100;//可以,但是沒(méi)有意義
System.out.print(i); //不可以
}
static int i = 2 ;
public static void main(String[] args) {
System.out.println(i); // 2
}
<clinit>()和<init>()是不同的,前者對(duì)應(yīng)的是類(lèi),后者對(duì)應(yīng)的是實(shí)例,即前一個(gè)是Class的構(gòu)造器(是編譯器生成的),后者是實(shí)例對(duì)象的構(gòu)造器(也就是我們定義或繼承的構(gòu)造函數(shù))。且虛擬機(jī)會(huì)保證子類(lèi)的<clinit>()執(zhí)行之前,其父類(lèi)的<clinit>()一定執(zhí)行完成,無(wú)需顯式指定。所以第一個(gè)執(zhí)行<clinit>()的類(lèi)是java.lang.Object;- 因?yàn)楦割?lèi)比子類(lèi)先執(zhí)行
<clinit>(),所以父類(lèi)的靜態(tài)變量和靜態(tài)代碼塊是先于子類(lèi)執(zhí)行的。- 如果一個(gè)類(lèi)或者接口中沒(méi)有靜態(tài)變量或靜態(tài)代碼塊,編譯器可以不生成
<clinit>()- 接口中沒(méi)有靜態(tài)代碼塊,但是可以有靜態(tài)變量。所以可以有
<clinit>()的初始化動(dòng)作,但是接口和類(lèi)不同之處在于接口不需要先執(zhí)行父接口的<clinit>()方法。- 虛擬機(jī)會(huì)保證一個(gè)類(lèi)的
<clinit>()方法在多線程環(huán)境中能被正確的同步,利用這一點(diǎn)可以實(shí)現(xiàn)線程安全的單例模式。
初始化結(jié)束后,一個(gè)類(lèi)就可以被正常的使用了。
參考文獻(xiàn)
《深入理解Java虛擬機(jī)》周志明
The end.