1 什么是JVM?
JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫,是通過(guò)在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來(lái)實(shí)現(xiàn)的。由一套字節(jié)碼指令集、一組寄存器、一個(gè)棧、一個(gè)垃圾回收堆和一個(gè)存儲(chǔ)方法域等組成。JVM屏蔽了與操作系統(tǒng)平臺(tái)相關(guān)的信息,使得Java程序只需要生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可在多種平臺(tái)上不加修改的運(yùn)行,這也是Java能夠“一次編譯,到處運(yùn)行的”原因。
2 JRE、JDK和JVM的關(guān)系
JRE(Java Runtime Environment, Java運(yùn)行環(huán)境)是Java平臺(tái),所有的程序都要在JRE下才能夠運(yùn)行。包括JVM和Java核心類庫(kù)和支持文件。
JDK(Java Development Kit,Java開發(fā)工具包)是用來(lái)編譯、調(diào)試Java程序的開發(fā)工具包。包括Java工具(javac/java/jdb等)和Java基礎(chǔ)的類庫(kù)(java API )。
JVM(Java Virtual Machine, Java虛擬機(jī))是JRE的一部分。JVM主要工作是解釋自己的指令集(即字節(jié)碼)并映射到本地的CPU指令集和OS的系統(tǒng)調(diào)用。Java語(yǔ)言是跨平臺(tái)運(yùn)行的,不同的操作系統(tǒng)會(huì)有不同的JVM映射規(guī)則,使之與操作系統(tǒng)無(wú)關(guān),完成跨平臺(tái)性。
下圖表示了JDK、JRE和JVM三者間的關(guān)系:
總結(jié):使用JDK(調(diào)用JAVA API)開發(fā)JAVA程序后,通過(guò)JDK中的編譯程序(javac)將Java程序編譯為Java字節(jié)碼,在JRE上運(yùn)行這些字節(jié)碼,JVM會(huì)解析并映射到真實(shí)操作系統(tǒng)的CPU指令集和OS的系統(tǒng)調(diào)用。
3 JVM原理
Java 體系結(jié)構(gòu)介紹:
Class Loader(類加載器):用于裝載.class文件。
Execution Engine(執(zhí)行引擎):用于執(zhí)行字節(jié)碼或者本地方法。
運(yùn)行時(shí)數(shù)據(jù)區(qū):方法區(qū)、堆、java棧、pc寄存器、本地方法棧。
JVM生命周期介紹:
Java實(shí)例對(duì)應(yīng)一個(gè)獨(dú)立運(yùn)行的Java程序(進(jìn)程級(jí)別)
1.啟動(dòng)。啟動(dòng)一個(gè)Java程序,一個(gè)JVM實(shí)例就產(chǎn)生。擁有public static void main(String[] args)函數(shù)的class可以作為JVM實(shí)例運(yùn)行的起點(diǎn)。
2.運(yùn)行。main()作為程序初始線程的起點(diǎn),任何其他線程均可由該線程啟動(dòng)。JVM內(nèi)部有兩種線程:守護(hù)線程和非守護(hù)線程,main()屬于非守護(hù)線程,守護(hù)線程通常由JVM使用,程序可以指定創(chuàng)建的線程為守護(hù)線程。
3.消亡。當(dāng)程序中的所有非守護(hù)線程都終止時(shí),JVM才退出;若安全管理器允許,程序也可以使用Runtime類或者System.exit()來(lái)退出。
JVM執(zhí)行引擎實(shí)例則對(duì)應(yīng)了屬于用戶運(yùn)行程序線程它是線程級(jí)別的。
Java類加載器:
Java加載類的過(guò)程:
1.裝載(loading):負(fù)責(zé)找到二進(jìn)制字節(jié)碼并加載至JVM中,JVM通過(guò)類名、類所在的包名、ClassLoader完成類的加載。因此,標(biāo)識(shí)一個(gè)被加載了的類:類名 + 包名 + ClassLoader實(shí)例ID。
2.鏈接(linking):負(fù)責(zé)對(duì)二進(jìn)制字節(jié)碼的格式進(jìn)行校驗(yàn)、初始化裝載類中的靜態(tài)變量以及解析類中調(diào)用的接口。
完成校驗(yàn)后,JVM初始化類中的靜態(tài)變量,并將其賦值為默認(rèn)值。
最后對(duì)比類中的所有屬性、方法進(jìn)行驗(yàn)證,以確保要調(diào)用的屬性、方法存在,以及具備訪問(wèn)權(quán)限(例如private、public等),否則會(huì)造成NoSuchMethodError、NoSuchFieldError等錯(cuò)誤信息。
3.初始化(initializing):負(fù)責(zé)執(zhí)行類中的靜態(tài)初始化代碼、構(gòu)造器代碼以及靜態(tài)屬性的初始化,以下四種情況初始化過(guò)程會(huì)被觸發(fā)。
調(diào)用 new
反射調(diào)用了類中的方法
子類調(diào)用了初始化
JVM啟動(dòng)過(guò)程終止定的初始化類
JVM類加載順序:
層級(jí)結(jié)構(gòu)
1.Booststrap ClassLoader
跟ClassLoader,C++實(shí)現(xiàn),JVM啟動(dòng)時(shí)初始化此ClassLoader,并由此完成$JAVA_HONE中jre/lib/rt.jar(Sun JDK的實(shí)現(xiàn))中所有class文件的加載,這個(gè)jar中包含了java規(guī)范定義的所有接口以及實(shí)現(xiàn)。
2.Extension ClassLoader
JVM用此classloader來(lái)加載擴(kuò)展功能的一些jar包
3.System ClassLoader
JVM用此ClassLoader來(lái)加載啟動(dòng)參數(shù)中指定的ClassPath中的jar包以及目錄,在Sun JDK中ClassLoader對(duì)應(yīng)的類名為AppClassLoader。
4.User-Defined ClassLoader
User-Defined ClassLoader是Java開發(fā)人員繼承ClassLoader抽象類實(shí)現(xiàn)的ClassLoader,基于自定義的ClassLoader可用于加載非ClassPath中的jar以及目錄。
委派模式(Delegation Mode)
當(dāng)JVM加載一個(gè)類的時(shí)候,下層的加載器會(huì)將任務(wù)給上一層類加載器,上一層加載檢查它的命名空間中是否已經(jīng)加載這個(gè)類,如果已經(jīng)加載,直接使用這個(gè)類。如果沒有加載,繼續(xù)往上委托直到頂部。檢查之后,按照相反的順序進(jìn)行加載。如果Bootstrap加載器不到這個(gè)類,則往下委托,直到找到這個(gè)類。一個(gè)類可以被不同的類加載器加載。
可見性限制:下層的加載器能夠看到上層加載器中的類,反之則不行,委派只能從下到上。
不允許卸載類:類加載器可以加載一個(gè)類,但不能夠卸載一個(gè)類。但是類加載器可以被創(chuàng)建或者刪除。
JVM執(zhí)行引擎
類加載器將字節(jié)碼載入內(nèi)存后,執(zhí)行引擎以java字節(jié)碼為單元,讀取java字節(jié)碼。java字節(jié)碼機(jī)器讀不懂,必須將字節(jié)碼轉(zhuǎn)化為平臺(tái)相關(guān)的機(jī)器碼。這個(gè)過(guò)程就是由執(zhí)行引擎完成的。
在執(zhí)行方法時(shí)JVM提供了四種指令來(lái)執(zhí)行:
invokestatic:調(diào)用類的static方法。
invokevirtual:調(diào)用對(duì)象實(shí)例的方法。
invokeinterface:將屬性定義為接口來(lái)進(jìn)行調(diào)用。
invokespecial:JVM對(duì)于初始化對(duì)象(Java構(gòu)造器的方法為:)以及調(diào)用對(duì)象實(shí)例的私有方法時(shí)。
主要的執(zhí)行計(jì)數(shù):
解釋,即時(shí)執(zhí)行,自適應(yīng)優(yōu)化、芯片級(jí)直接執(zhí)行。
解釋屬于第一代JVM
即時(shí)編譯JIT屬于第二代JVM
自適應(yīng)優(yōu)化(目前sun的HotspotJVM采用這種技術(shù)),吸取第一代JVM和第二代JVM的經(jīng)驗(yàn),采用兩者結(jié)合的方式,開始對(duì)所有的代碼都采用解釋執(zhí)行的方式,并監(jiān)視代碼執(zhí)行情況,然后對(duì)那些經(jīng)常調(diào)用的方法啟動(dòng)一個(gè)后臺(tái)線程,將其編譯為本地代碼,并進(jìn)行優(yōu)化。若方法不再頻繁使用,則取消編譯過(guò)代碼,仍對(duì)其進(jìn)行解釋執(zhí)行。
Java運(yùn)行時(shí)數(shù)據(jù)區(qū)
PC寄存器
用于存儲(chǔ)每個(gè)線程下一步將要執(zhí)行的JVM指令,若該方法為native的,則PC寄存器中不存儲(chǔ)任何信息。Java多線程情況下,每個(gè)線程都有一個(gè)自己的PC,以便完成不同線程上下文環(huán)境的切換。
JVM棧
JVM棧是線程私有的,每個(gè)線程創(chuàng)建的同時(shí)都會(huì)創(chuàng)建JVM棧,JVM棧中存放當(dāng)前線程中局部基本類型的變量(Java中定義的八種基本類型:boolean、char、byte、short、int、long、float、double)、部分的返回結(jié)果以及Stack Frame,非基本類型的對(duì)象在JVM棧上僅存放一個(gè)指向堆的地址。
堆(Heap)
它是JVM用來(lái)存儲(chǔ)對(duì)象實(shí)例以及數(shù)組值的區(qū)域,可以認(rèn)為Java中所有通過(guò)new創(chuàng)建的對(duì)象的內(nèi)存都在此分配,Heap中的對(duì)象的內(nèi)存需要等待GC進(jìn)行回收。
堆在JVM啟動(dòng)的時(shí)候就被創(chuàng)建,堆中儲(chǔ)存了各種對(duì)象,這些對(duì)象被自動(dòng)管理內(nèi)存系統(tǒng)(Automatic Storage Management System),也就是常說(shuō)的“Garbage Collector(垃圾回收器)”管理。這些對(duì)象無(wú)需、也無(wú)法顯示地被銷毀。
JVM將Heap分為兩塊:新生代New Generation和舊生代Old Generation
堆是JVM中所有線程共享的,因此在其上進(jìn)行對(duì)象內(nèi)存的分配均需要進(jìn)行加鎖,導(dǎo)致new對(duì)象的開銷比較大。
Sun Hotspot JVM為了提升對(duì)象內(nèi)存分配的效率,對(duì)于所有創(chuàng)建的線程都會(huì)分配一塊獨(dú)立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據(jù)運(yùn)行的情況計(jì)算而得,在TLAB上分配對(duì)象時(shí)不需要加鎖,因此JVM在給線程對(duì)象分配內(nèi)存時(shí)會(huì)盡量的在TLAB上分配,在這種情況下JVM中分配對(duì)象內(nèi)存的性能和C基本是一樣的,但如果對(duì)象過(guò)大的話則仍然要直接使用堆空間分配。
TLAB僅作用于新生代的Eden Space,因此在編寫Java程序時(shí),通常多個(gè)小的對(duì)象比大的對(duì)象分配起來(lái)更加高效。
所有新創(chuàng)建的Object都將會(huì)存儲(chǔ)在新生代Young Generation中。如果Young Generation的數(shù)據(jù)在一次或多次GC后存活下來(lái),那么將被轉(zhuǎn)移到OldGeneration。新的Object總是創(chuàng)建在Eden Space。
方法區(qū)域(Method Area)
在Sun JDK中這塊區(qū)域?qū)?yīng)的為PermanetGeneration,又稱為持久代。
方法區(qū)域存放所加載類的信息(名稱、修飾符等)、類中的靜態(tài)變量、類中定義為final類型的常量、類中的Field信息、類中的方法信息,當(dāng)開發(fā)人員在程序中通過(guò)Class對(duì)象中的getName,isInstance等方法來(lái)獲取信息時(shí),這些數(shù)據(jù)都來(lái)源于方法區(qū)域,同時(shí)方法區(qū)域也是全局共享的,在一定條件下它也會(huì)被GC,當(dāng)方法區(qū)域需要使用的內(nèi)存超過(guò)其允許的大小時(shí),就會(huì)拋出OutOfMemory的錯(cuò)誤信息。
運(yùn)行時(shí)常量池(Runtime Constant Pool)
存放的為類中的固定常量信息、方法和Field的引用信息等,其空間從方法區(qū)域中分配。
本地方法堆棧(Native Method Stacks)
JVM采用本地方法堆來(lái)支持native方法的執(zhí)行,此區(qū)域用于存儲(chǔ)每個(gè)native方法調(diào)用的狀態(tài)。
JVM垃圾回收
GC的基本原理:將內(nèi)存中不再被使用的對(duì)象進(jìn)行回收,GC中用于回收的方法稱為收集器,由于GC需要消耗一些資源和時(shí)間,Java在對(duì)對(duì)象生命周期特征進(jìn)行分析后,按照新生代、舊生代的方式來(lái)對(duì)對(duì)象進(jìn)行收集,以盡可能的縮短GC對(duì)應(yīng)用造成的暫停。
對(duì)新生代的對(duì)象收集稱為minor GC
對(duì)舊生代的對(duì)象收集稱為Full GC
程序中主動(dòng)調(diào)用System.gc()強(qiáng)制執(zhí)行的GC為Full GC。
不同的對(duì)象引用類型,GC會(huì)采用不同的方法進(jìn)行回收,JVM對(duì)象的引用分為了四種類型:
強(qiáng)引用:默認(rèn)情況下,對(duì)象采用的均為強(qiáng)引用(這個(gè)對(duì)象的實(shí)例沒有其他對(duì)象引用時(shí), GC時(shí)才會(huì)被回收)
軟引用:軟引用是Java中提供的一種比較適合于緩存場(chǎng)景的應(yīng)用(只有內(nèi)存不夠的情況下才會(huì)被GC)
弱引用:在GC時(shí)一定會(huì)被GC回收。
虛引用:虛引用只是用來(lái)得知對(duì)象是否被GC。
評(píng)論
查看更多