一涌乳、java文件運(yùn)行的全部過(guò)程是怎么樣的呢布疼?
當(dāng)一個(gè)classLoder啟動(dòng)的時(shí)候燕锥,classLoader的生存地點(diǎn)在jvm中的堆赚楚,然后它會(huì)去主機(jī)硬盤上將A.class裝載到j(luò)vm的方法區(qū)淌友,方法區(qū)中的這個(gè)字節(jié)文件會(huì)被虛擬機(jī)拿來(lái)new A字節(jié)碼()煌恢,然后在堆內(nèi)存生成了一個(gè)A字節(jié)碼的對(duì)象,然后A字節(jié)碼這個(gè)內(nèi)存文件有兩個(gè)引用一個(gè)指向A的class對(duì)象震庭,一個(gè)指向加載自己的classLoader
java虛擬機(jī)的生命周期:聲明周期起點(diǎn)是當(dāng)一個(gè)java應(yīng)用main函數(shù)啟動(dòng)時(shí)虛擬機(jī)也同時(shí)被啟動(dòng)瑰抵,而只有當(dāng)在虛擬機(jī)實(shí)例中的所有非守護(hù)進(jìn)程都結(jié)束時(shí),java虛擬機(jī)實(shí)例才結(jié)束生命器联。
java的虛擬機(jī)種有兩種線程二汛,一種叫叫守護(hù)線程婿崭,一種叫非守護(hù)線程(也叫普通線程),main函數(shù)就是個(gè)非守護(hù)線程肴颊,虛擬機(jī)的gc就是一個(gè)守護(hù)線程氓栈。java的虛擬機(jī)中,只要有任何非守護(hù)線程還沒(méi)有結(jié)束婿着,java虛擬機(jī)的實(shí)例都不會(huì)退出授瘦,所以即使main函數(shù)這個(gè)非守護(hù)線程退出,但是由于在main函數(shù)中啟動(dòng)的匿名線程也是非守護(hù)線程竟宋,它還沒(méi)有結(jié)束提完,所以jvm沒(méi)辦法退出
java源文件到最后運(yùn)行起來(lái),主要會(huì)經(jīng)過(guò)兩個(gè)階段:
1丘侠、.java文件通過(guò)java編譯器(javac.exe)編譯成.class文件氯葬,這個(gè)就是所說(shuō)的字節(jié)碼文件。
2婉陷、.class文件加載到j(luò)vm進(jìn)行運(yùn)行
首先帚称,當(dāng)一個(gè)程序啟動(dòng)之前,它的class會(huì)被類裝載器裝入方法區(qū)(不好聽(tīng)秽澳,其實(shí)這個(gè)區(qū)我喜歡叫做Permanent區(qū))闯睹,執(zhí)行引擎讀取方法區(qū)的字節(jié)碼自適應(yīng)解析,邊解析就邊運(yùn)行(其中一種方式)担神,然后pc寄存器指向了main函數(shù)所在位置楼吃,虛擬機(jī)開(kāi)始為main函數(shù)在java棧中預(yù)留一個(gè)棧幀(每個(gè)方法都對(duì)應(yīng)一個(gè)棧幀),然后開(kāi)始跑main函數(shù)妄讯,main函數(shù)里的代碼被執(zhí)行引擎映射成本地操作系統(tǒng)里相應(yīng)的實(shí)現(xiàn)孩锡,然后調(diào)用本地方法接口,本地方法運(yùn)行的時(shí)候亥贸,操縱系統(tǒng)會(huì)為本地方法分配本地方法棧躬窜,用來(lái)儲(chǔ)存一些臨時(shí)變量,然后運(yùn)行本地方法炕置,調(diào)用操作系統(tǒng)APIi等等荣挨。
jvm內(nèi)部執(zhí)行運(yùn)行流程圖:
JVM運(yùn)行簡(jiǎn)易過(guò)程:
上圖左半部分其實(shí)不是在JVM中,程序員在eclipse上寫的是.java文件朴摊,經(jīng)過(guò)編譯成.class文件(比如maven工程需要maven install默垄,打成jar報(bào),jar包里面都是.calss文件)甚纲;這些步驟都是在eclipse上進(jìn)行的口锭。然后類加載器(classloader)一直到解釋器是屬于JVM的
二、JVM是如何來(lái)處理.class文件的
首先來(lái)分析下JVM到底是個(gè)怎么樣的東西
JVM被分為三個(gè)主要的子系統(tǒng)
(1)類加載器子系統(tǒng)(2)運(yùn)行時(shí)數(shù)據(jù)區(qū)(3)執(zhí)行引擎
1. 類加載器子系統(tǒng)
加載介杆、驗(yàn)證鹃操、準(zhǔn)備和初始化這四個(gè)階段發(fā)生的順序是確定的况既,而解析階段則不一定,它在某些情況下可以在初始化階段之后開(kāi)始组民,這是為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定(也成為動(dòng)態(tài)綁定或晚期綁定)棒仍。另外注意這里的幾個(gè)階段是按順序開(kāi)始,而不是按順序進(jìn)行或完成臭胜,因?yàn)檫@些階段通常都是互相交叉地混合進(jìn)行的莫其,通常在一個(gè)階段執(zhí)行的過(guò)程中調(diào)用或激活另一個(gè)階段。
?加載
jvm系列(一):java類的加載機(jī)制 - 純潔的微笑 - 博客園?www.cnblogs.com
加載是類加載過(guò)程的第一個(gè)階段耸三,在加載階段乱陡,虛擬機(jī)需要完成以下三件事情:
① 通過(guò)一個(gè)類的全限定名來(lái)獲取其定義的二進(jìn)制字節(jié)流。
② 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)仪壮。
③ 在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象憨颠,作為對(duì)方法區(qū)中這些數(shù)據(jù)的訪問(wèn)入口。
相對(duì)于類加載的其他階段而言积锅,加載階段(準(zhǔn)確地說(shuō)爽彤,是加載階段獲取類的二進(jìn)制字節(jié)流的動(dòng)作)是可控性最強(qiáng)的階段,因?yàn)殚_(kāi)發(fā)人員既可以使用系統(tǒng)提供的類加載器來(lái)完成加載缚陷,也可以自定義自己的類加載器來(lái)完成加載适篙。
加載階段完成后,虛擬機(jī)外部的 二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)之中箫爷,而且在Java堆中也創(chuàng)建一個(gè)java.lang.Class類的對(duì)象嚷节,這樣便可以通過(guò)該對(duì)象訪問(wèn)方法區(qū)中的這些數(shù)據(jù)。
?連接
1)驗(yàn)證:確保被加載的類的正確性
驗(yàn)證是連接階段的第一步虎锚,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求硫痰,并且不會(huì)危害虛擬機(jī)自身的安全。驗(yàn)證階段大致會(huì)完成4個(gè)階段的檢驗(yàn)動(dòng)作:
文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范窜护;例如:是否以0xCAFEBABE開(kāi)頭效斑、主次版本號(hào)是否在當(dāng)前虛擬機(jī)的處理范圍之內(nèi)、常量池中的常量是否有不被支持的類型柄慰。
元數(shù)據(jù)驗(yàn)證:對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析(注意:對(duì)比javac編譯階段的語(yǔ)義分析)鳍悠,以保證其描述的信息符合Java語(yǔ)言規(guī)范的要求税娜;例如:這個(gè)類是否有父類坐搔,除了java.lang.Object之外。
字節(jié)碼驗(yàn)證:通過(guò)數(shù)據(jù)流和控制流分析敬矩,確定程序語(yǔ)義是合法的概行、符合邏輯的。
符號(hào)引用驗(yàn)證:確保解析動(dòng)作能正確執(zhí)行弧岳。
驗(yàn)證階段是非常重要的凳忙,但不是必須的业踏,它對(duì)程序運(yùn)行期沒(méi)有影響,如果所引用的類經(jīng)過(guò)反復(fù)驗(yàn)證涧卵,那么可以考慮采用-Xverifynone參數(shù)來(lái)關(guān)閉大部分的類驗(yàn)證措施勤家,以縮短虛擬機(jī)類加載的時(shí)間。
2)準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存柳恐,并將其初始化為默認(rèn)值
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段伐脖,這些內(nèi)存都將在方法區(qū)中分配。對(duì)于該階段有以下幾點(diǎn)需要注意:
① 這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(static)乐设,而不包括實(shí)例變量讼庇,實(shí)例變量會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一塊分配在Java堆中。
② 這里所設(shè)置的初始值通常情況下是數(shù)據(jù)類型默認(rèn)的零值(如0近尚、0L蠕啄、null、false等)戈锻,而不是被在Java代碼中被顯式地賦予的值歼跟。
假設(shè)一個(gè)類變量的定義為:public static int value = 3;
那么變量value在準(zhǔn)備階段過(guò)后的初始值為0格遭,而不是3嘹承,因?yàn)檫@時(shí)候尚未開(kāi)始執(zhí)行任何Java方法,而把value賦值為3的putstatic指令是在程序編譯后如庭,存放于類構(gòu)造器<clinit>()方法之中的叹卷,所以把value賦值為3的動(dòng)作將在初始化階段才會(huì)執(zhí)行
3)解析:把類中的符號(hào)引用轉(zhuǎn)換為直接引用
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程,解析動(dòng)作主要針對(duì)類或接口坪它、字段骤竹、類方法、接口方法往毡、方法類型蒙揣、方法句柄和調(diào)用點(diǎn)限定符7類符號(hào)引用進(jìn)行。符號(hào)引用就是一組符號(hào)來(lái)描述目標(biāo)开瞭,可以是任何字面量懒震。
直接引用就是直接指向目標(biāo)的指針、相對(duì)偏移量或一個(gè)間接定位到目標(biāo)的句柄嗤详。
- 初始化
類初始化階段是類加載的最后一步个扰,前面的類加載過(guò)程中,除了在加載階段用戶用程序可以通過(guò)自定義類加載器參與之外葱色,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制递宅。到了初始化階段,才真正開(kāi)始執(zhí)行類中定義的Java程序代碼(或者說(shuō)字節(jié)碼)。這里所有的靜態(tài)變量會(huì)被賦初始值, 并且靜態(tài)塊將被執(zhí)行办龄。
java中烘绽,對(duì)于初始化階段,有且只有以下五種情況才會(huì)對(duì)要求類立刻初始化:
① 使用new關(guān)鍵字實(shí)例化對(duì)象俐填、訪問(wèn)或者設(shè)置一個(gè)類的靜態(tài)字段(被final修飾安接、編譯器優(yōu)化時(shí)已經(jīng)放入常量池的例外)、調(diào)用類方法英融,都會(huì)初始化該靜態(tài)字段或者靜態(tài)方法所在的類赫段;
② 初始化類的時(shí)候,如果其父類沒(méi)有被初始化過(guò)矢赁,則要先觸發(fā)其父類初始化糯笙;
③ 使用java.lang.reflect包的方法進(jìn)行反射調(diào)用的時(shí)候,如果類沒(méi)有被初始化撩银,則要先初始化给涕;
④ 虛擬機(jī)啟動(dòng)時(shí),用戶會(huì)先初始化要執(zhí)行的主類(含有main)额获;
⑤ jdk 1.7后够庙,如果java.lang.invoke.MethodHandle的實(shí)例最后對(duì)應(yīng)的解析結(jié)果是 REF_getStatic、REF_putStatic抄邀、REF_invokeStatic方法句柄耘眨,并且這個(gè)方法所在類沒(méi)有初始化,則先初始化境肾;
2剔难、運(yùn)行時(shí)數(shù)據(jù)
運(yùn)行時(shí)數(shù)據(jù)區(qū)域被劃分為5個(gè)主要組件:
① 方法區(qū) (線程共享) 常量 靜態(tài)變量 JIT(即時(shí)編譯器)編譯后代碼也在方法區(qū)存放
② 堆內(nèi)存(線程共享) 垃圾回收的主要場(chǎng)地
③ 程序計(jì)數(shù)器 當(dāng)前線程執(zhí)行的字節(jié)碼的位置指示器
④ Java虛擬機(jī)棧(棧內(nèi)存) :保存局部變量,基本數(shù)據(jù)類型以及堆內(nèi)存中對(duì)象的引用變量
⑤ 本地方法棧 (C棧):為JVM提供使用native方法的服務(wù)
3、執(zhí)行引擎
分配給運(yùn)行時(shí)數(shù)據(jù)區(qū)的字節(jié)碼將由執(zhí)行引擎執(zhí)行奥喻。執(zhí)行引擎讀取字節(jié)碼并逐段執(zhí)行偶宫。
① 解釋器: 解釋器能快速的解釋字節(jié)碼,但執(zhí)行卻很慢环鲤。 解釋器的缺點(diǎn)就是,當(dāng)一個(gè)方法被調(diào)用多次纯趋,每次都需要重新解釋。
② 即時(shí)(Just-In-Time)編譯器:JIT編譯器消除了解釋器的缺點(diǎn)冷离。執(zhí)行引擎利用解釋器轉(zhuǎn)換字節(jié)碼明也,但如果是重復(fù)的代碼則使用JIT編譯器將全部字節(jié)碼編譯成本機(jī)代碼倡鲸。
簡(jiǎn)單理解JIT就是當(dāng)代碼中某些方法復(fù)用次數(shù)比較高的规揪,并超過(guò)一個(gè)特定的值就成為了“熱點(diǎn)代碼”犯眠。那么這個(gè)這些熱點(diǎn)代碼就會(huì)被編譯成本地代碼(其實(shí)可以理解成緩存)加快訪問(wèn)速度甫菠。