小編今天帶大家了解如何進(jìn)行JVM體系結(jié)構(gòu)分析,文中知識點(diǎn)介紹的非常詳細(xì)。覺得有幫助的朋友可以跟著小編一起瀏覽文章的內(nèi)容,希望能夠幫助更多想解決這個問題的朋友找到問題的答案,下面跟著小編一起深入學(xué)習(xí)“如何進(jìn)行JVM體系結(jié)構(gòu)分析”的知識吧。
成都創(chuàng)新互聯(lián)公司主營縉云網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,成都App制作,縉云h5小程序制作搭建,縉云網(wǎng)站營銷推廣歡迎縉云等地區(qū)企業(yè)咨詢
何為虛擬機(jī)呢?虛擬機(jī)是模擬執(zhí)行某種指令集體系結(jié)構(gòu)(ISA)的軟件,是對操作系統(tǒng)和硬件的一種抽象。其軟件模型如下圖所示:
計(jì)算機(jī)系統(tǒng)的這種抽象類似于面向?qū)ο缶幊?OOP)中的針對接口編程泛型(或者是依賴倒轉(zhuǎn)原則),通過一層抽象提取底層實(shí)現(xiàn)中共性的部分,底層實(shí)現(xiàn)這個抽象并完成自己個性的部分。也就是說通過一個抽象層次來隔離底層的不同實(shí)現(xiàn)。虛擬機(jī)規(guī)范定義了這個虛擬機(jī)要完成的功能(也就是接口),底層的操作系統(tǒng)和硬件利用自己提供的功能來實(shí)現(xiàn)虛擬機(jī)需要完成的功能(實(shí)現(xiàn))。通過運(yùn)行在虛擬機(jī)之上,Java才具有很好跨平臺特性。
Java虛擬機(jī)(JVM)是由Java虛擬機(jī)規(guī)范定義的,其上運(yùn)行的是字節(jié)碼指令集。這種字節(jié)碼指令集包含一個字節(jié)的操作碼(opcode),零至多個操作數(shù)(oprand),虛擬機(jī)規(guī)范明確定義了每種字節(jié)碼指令完成的功能是什么以及需要多少個操作數(shù)。Java虛擬機(jī)上運(yùn)行的class文件,這個文件中包含字節(jié)碼指令流以及類定義的信息,所以Java虛擬機(jī)規(guī)范還定義了class文件的格式(精確到每個字節(jié))。所以實(shí)現(xiàn)Java虛擬機(jī)的兩個要素是字節(jié)碼指令集和class文件格式,Java虛擬機(jī)的實(shí)現(xiàn)者只要以正確方式讀取class文件中的每一條字節(jié)碼指令,并按照要求實(shí)現(xiàn)字節(jié)碼指令的功能就可以實(shí)現(xiàn)JVM。
目前常用的商用JVM主要有:Sun HotSpot,BEA JRocket以及IBM J9。其中由于BEA和Sun已經(jīng)被Oracle收購,所以O(shè)racle擁有當(dāng)今世界上最流行的兩個JVM,并有傳言說Oracle將在Java8時將兩個虛擬機(jī)合并,各取所需,取長補(bǔ)短,打造一個更加精湛的JVM。HotSpot會以解釋+即時編譯執(zhí)行代碼,HotSpot在解釋執(zhí)行字節(jié)碼的時候,會探測熱點(diǎn)(hotspot)代碼,然后將這部分代碼編譯為本地代碼,之后將直接運(yùn)行本地代碼,而不是解釋,這樣會有效提高虛擬機(jī)性能。JRocket主要是定位于服務(wù)器應(yīng)用,所以不關(guān)注虛擬機(jī)的啟動速度,它會將所有代碼即時編譯為本地代碼執(zhí)行,JRocket的垃圾收集器具有很高的收集效率。J9定位與HotSpot類似,專注于桌面應(yīng)用和服務(wù)器應(yīng)用,主要是針對IBM的各種Java產(chǎn)品。
我們知道Java源代碼,即.java文件,通過javac編譯為.class文件。.class文件可以運(yùn)行在JVM上,JVM底層會通過字節(jié)碼解釋器或者即時編譯器(JIT Compiler)執(zhí)行.class文件中的字節(jié)碼指令。JVM是運(yùn)行在操作系統(tǒng)之上的,操作系統(tǒng)又通過指令集調(diào)用底層硬件服務(wù)執(zhí)行其上的各種軟件。
可以看到Java是運(yùn)行在JVM之上的。但是Java語言和JVM沒有必然的聯(lián)系。Java語言并不是只能運(yùn)行在JVM之上,只要實(shí)現(xiàn)了相應(yīng)的編譯器Java語言就可以運(yùn)行在任何平臺之上(比如J++),也可以被編譯為本地代碼直接運(yùn)行在操作系統(tǒng)之上,比如,Linux上的GCJ(GNU Compiler for Java)就可以把Java語言編譯為本地代碼直接執(zhí)行。同樣的,JVM上也不是只能執(zhí)行Java語言,只要實(shí)現(xiàn)了適當(dāng)?shù)木幾g器,將其他語言編譯為JVM上的字節(jié)碼,就可以在JVM上運(yùn)行。比如,JRuby,Jython以及Groovy等其他JVM語言,都會通過相應(yīng)的編譯器或是解釋器轉(zhuǎn)化為.class,然后再JVM上運(yùn)行。由于JVM并不關(guān)心.class文件是由Java、JRuby、Jython等轉(zhuǎn)化而來,只要這個文件結(jié)構(gòu)正確并能通過class文件校驗(yàn)。因此,由于.class文件屏蔽了Java、JRuby等上層語言的差異,所以Java、Groovy等可以相互調(diào)用。
當(dāng)啟動一個Java程序時,一個虛擬機(jī)實(shí)例就誕生了,當(dāng)程序關(guān)閉退出時,這個虛擬機(jī)實(shí)例隨之消亡。JVM實(shí)例通過main()方法來運(yùn)行一個Java程序。而這個main()方法必須是共有的(public)、靜態(tài)的(static)、返回void,并且接收一個字符串?dāng)?shù)組為參數(shù)。Java程序初始類中的main()方法,將作為改程序初始線程的起點(diǎn),任何其他線程都是由這個初試線程啟動的。
JVM內(nèi)部有兩種線程:守護(hù)線程與非守護(hù)線程。守護(hù)線程通常是由虛擬機(jī)自己使用的,比如垃圾回收線程。當(dāng)該程序所有的非守護(hù)線程都終止時,JVM實(shí)例將自動退出。
JVM由類加載器子系統(tǒng),運(yùn)行時數(shù)據(jù)區(qū),執(zhí)行引擎以及本地方法接口組成。
類加載器子系統(tǒng)主要用于定位類定義的二進(jìn)制信息,然后將這些信息解析并加載至虛擬機(jī),轉(zhuǎn)化為虛擬機(jī)內(nèi)部的類型信息的數(shù)據(jù)結(jié)構(gòu)。類加載器子系統(tǒng)還承擔(dān)著安全性的責(zé)任,并且是JVM的動態(tài)鏈接和動態(tài)加載的基礎(chǔ)。將二進(jìn)制信息=>類型信息的數(shù)據(jù)結(jié)構(gòu),中間需要經(jīng)過很多步驟。首先類加載器是JVM安全沙箱的第一道防線,能夠防止非信任類破壞虛擬機(jī)。每一個被加載的class文件需要經(jīng)過四次校驗(yàn)才能被加載。校驗(yàn)通過后,類加載器的命名空間和運(yùn)行時包的特性能夠防止非信任類偽裝成信任類來破壞虛擬機(jī)。類加載器在方法區(qū)構(gòu)造具有這個類的信息的數(shù)據(jù)結(jié)構(gòu)后,會在堆上創(chuàng)建一個Class對象作為訪問這個數(shù)據(jù)結(jié)構(gòu)的接口。同時,類加載還需要初始化類的靜態(tài)數(shù)據(jù),也就是調(diào)用類的方法。以上就是一個類的加載、鏈接及初始化的過程。
運(yùn)行時數(shù)據(jù)區(qū)是JVM運(yùn)行時的內(nèi)存空間的組織,邏輯上又劃分為多個區(qū),這些區(qū)的生命周期和它是否線程共享有關(guān),它們分別是:
用于存放對象或數(shù)組實(shí)例,也就是運(yùn)行期間new出來的對象。堆的生命周期與JVM相同,并且在線程之間共享訪問。由于多線程并發(fā)訪問,所以需要考慮線程安全的問題,有兩種方法。第一種是,加鎖進(jìn)行互斥訪問。第二種是線程本地分配緩沖(Thread Local Allocate Buffer, TLAB),在線程創(chuàng)建時預(yù)先給每個線程分配一塊區(qū)域,這塊區(qū)域是線程私有的,對其他線程是不可見,也就不會被共享。JVM規(guī)范規(guī)定在申請不到足夠的內(nèi)存時,堆會拋出OutOfMemoryException。
存放類型信息和運(yùn)行時常量池(Runtime Constant Pool)。每個被類加載器加載的類都會在方法區(qū)中形成一個與子對應(yīng)的類型信息的數(shù)據(jù)結(jié)構(gòu),包括:這個類的類名、直接超類、實(shí)現(xiàn)的接口列表、字段列表、方法列表等。運(yùn)行時常量池是class文件中的常量池列表(Constant Pool List)在運(yùn)行時的一種體現(xiàn),其中存儲各種基本數(shù)據(jù)類型及String類型的常量以及其他類、方法、字段的符號引用。方法區(qū)的生命周期與JVM相同,被多個線程共享,所以要考慮并發(fā)訪問的安全性的問題。JVM規(guī)范規(guī)定在需要的內(nèi)存得不到滿足的情況下,方法區(qū)會拋出OutOfMemoryException。
線程私有的,生命周期與線程相同,是對CPU中PC的一種模擬。如果線程正在執(zhí)行的是Java方法,則該線程的PC中存放的下一條字節(jié)碼指令的地址。在進(jìn)行Java方法的調(diào)用和返回時,需要更新PC以保存當(dāng)前方法(Current Method)正在執(zhí)行的字節(jié)碼指令的地址。PC是JVM規(guī)范中唯一沒有規(guī)定會拋出異常的存儲區(qū)。
線程私有,生命周期與線程相同,是對傳統(tǒng)語言(比如C)中的方法調(diào)用棧的一種模擬。JVM棧中存放棧幀(Frame)用于進(jìn)行方法調(diào)用和返回、存儲局部變量以及計(jì)算的中間結(jié)果。JVM規(guī)范規(guī)定??梢話伋鰞煞N異常:(1)StackOverflowException,在棧的深度大于某個規(guī)定值的情況下拋出。(2)OutOfMemoryException,在為新棧幀分配內(nèi)存或者是為線程分配棧的內(nèi)存時,申請不到足夠的內(nèi)存的情況下拋出。
JVM棧中存放的是棧幀,每個棧幀對應(yīng)著一次方法調(diào)用。每一時刻,JVM線程只能執(zhí)行一個方法(Current Method),該方法的棧幀是JVM棧的棧頂?shù)脑?叫做當(dāng)前棧幀,Current Frame),當(dāng)調(diào)用一個方法時,會初始化一個棧幀壓入JVM棧;當(dāng)方法調(diào)用返回或者拋出異常沒有被處理的情況下,JVM棧會彈出該方法對應(yīng)的棧幀。每一個棧幀中存放局部變量表(Local Variable Table)、操作數(shù)棧(Oprand Stack)以及其他棧幀信息。棧幀的大小在編譯時就確定了,編譯器會把局部變量表和操作數(shù)棧的大小記錄在class文件中method_info的屬性表中。局部變量表類似于數(shù)組存放局部變量和方法參數(shù)。由于JVM采用的是基于棧的指令集體系結(jié)構(gòu),而不是基于寄存器,所以JVM上的所有計(jì)算都是在操作數(shù)棧上進(jìn)行的(比如,算術(shù)運(yùn)算、方法調(diào)用、內(nèi)存訪問等)。
用于支持本地方法調(diào)用,拋出的異常與JVM棧相同。
執(zhí)行引擎用于執(zhí)行JVM字節(jié)碼指令,主要由兩種實(shí)現(xiàn)方式:
(1)將輸入的字節(jié)碼指令在加載時或執(zhí)行時翻譯成另外一種虛擬機(jī)指令;
(2)將輸入的字節(jié)碼指令在加載時或執(zhí)行時翻譯成宿主主機(jī)本地CPU的指令集。這兩種方式對應(yīng)著字節(jié)碼的解釋執(zhí)行和即時編譯。比如在HotSpot VM中執(zhí)行引擎的實(shí)現(xiàn)是一種解釋-編譯的層次結(jié)構(gòu):
(1)解釋執(zhí)行:解釋執(zhí)行字節(jié)碼,并以方法為單位收集“熱點(diǎn)(HotSpot)代碼”的信息,將“熱點(diǎn)代碼”執(zhí)行C0編譯。
(2)C0編譯:將收集的“熱點(diǎn)代碼”編譯成本地代碼,并進(jìn)行一些簡單的優(yōu)化。繼續(xù)收集運(yùn)行時信息,將一些頻繁執(zhí)行的本地代碼進(jìn)行C1編譯。
(3)C1編譯:將C0階段的本地代碼,進(jìn)行一些比較激進(jìn)的優(yōu)化。如果某些優(yōu)化導(dǎo)致本地代碼執(zhí)行失敗,此時JVM會退化到解釋執(zhí)行字節(jié)碼階段。
自動內(nèi)存管理用于管理運(yùn)行時數(shù)據(jù)區(qū)的分配和釋放。和C和C++相比,Java不需要程序員主動的管理內(nèi)存(在new出對象后,不需要顯示的delete),這樣JVM就需要承擔(dān)內(nèi)存管理這個任務(wù)。內(nèi)存管理的重點(diǎn)主要是在申請內(nèi)存(new對象、類加載和初始化、啟動線程時初始化棧等)得不到滿足時,JVM可以自動回收那些不再存活的對象所占用的內(nèi)存,也就是經(jīng)常聽到的垃圾收集。在回收過程中還要保證處理內(nèi)存空間的碎片,以提高空間利用率?;厥者^程主要有兩個關(guān)鍵點(diǎn),標(biāo)記存活對象和回收內(nèi)存的算法。
標(biāo)記存活對象主要有引用計(jì)算和根搜索法兩種。
(1)引用計(jì)數(shù),是一種很普遍的方法,在python、lua等一些腳本語言中都是使用這種算法。每個對象持有一個計(jì)數(shù)器,標(biāo)記這個對象被引用的次數(shù)。進(jìn)行垃圾收集時,那些引用計(jì)數(shù)為0的對象就是“死”對象,需要被收集。引用計(jì)數(shù)的一個缺點(diǎn)就是它沒有辦法處理循環(huán)引用的情況(A->B, B->A)。
(2)根搜索,HotSpot虛擬機(jī)采用這種算法標(biāo)記存活對象。把方法區(qū)、JVM棧中的所有的引用組成的集合作為搜索的根,從這個集合開始遍歷直到結(jié)束。其中被遍歷到的對象是存活對象;那些沒有被遍歷到的對象需要被垃圾收集。這樣可以有效的避免循環(huán)引用的情況。
回收內(nèi)存的算法主要有:
(1)復(fù)制算法,將內(nèi)存分成兩個部分,每一時刻只是用其中的一個。進(jìn)行回收時,將所有存活的對象依次復(fù)制到另一個部分(依次復(fù)制避免了內(nèi)存碎片的產(chǎn)生),接下來只用這一個部分。復(fù)制算法需要在兩個內(nèi)存區(qū)域來回復(fù)制,有一定的復(fù)制開銷和空間開銷(每一時刻只使用一個區(qū)域),但是可以很好的解決內(nèi)存碎片的問題,適用于對象頻繁創(chuàng)建并且生命周期短的情況。
(2)標(biāo)記清掃,先進(jìn)行存活對象標(biāo)記,回收時將“死”對象占用的內(nèi)存直接釋放掉,會產(chǎn)生大量的內(nèi)存碎片。
(3)標(biāo)記整理,標(biāo)記階段與標(biāo)記清掃算法一樣,回收階段釋放“死”對象的內(nèi)存后,還需要進(jìn)行對象的移動使得所有對象依次在內(nèi)存中排列,避免了內(nèi)存碎片的產(chǎn)生。標(biāo)記整理與復(fù)制算法相反,適用于對象創(chuàng)建不頻繁,生命周期長得情況。
(4)按代收集,將內(nèi)存按照對象生命周期的不同劃分為多個部分,每個部分采用不同的收集算法。目前,大部分商業(yè)虛擬機(jī)都是采用這種算法。比如,在HotSpot中,內(nèi)存被劃分為:新生代(New)、老年代(Old)和永久代(Perm)。新生代采用復(fù)制算法,老年代和永久代采用標(biāo)記整理算法。內(nèi)存分配、回收的策略是,對象首先在新生代分配,如果新生代內(nèi)存不滿足要求,則觸發(fā)一次新生代內(nèi)存的垃圾收集(Young GC,或者是Minor GC)。Young GC會導(dǎo)致部分新生代的對象被移動至老年代,一部分是因?yàn)樾律鷥?nèi)存不足以放下所有的對象;另一部分是因?yàn)檫@些對象的年齡(每個對象都保存著這個對象被垃圾收集的次數(shù),表示它的年齡。存儲在對象頭的age屬性中)大到足以晉升到老年代。當(dāng)新生代的對象進(jìn)入老年代,而老年代的內(nèi)存不滿足要求時,則會觸發(fā)一次整個新生代和老年代的垃圾收集(Full GC, 或者是Major GC)。
在JVM中有多個后臺線程用于完成自動內(nèi)存管理,對于CPU來說這些后臺線程和用戶線程是一樣的,都需要占用系統(tǒng)的資源。在GC線程進(jìn)行垃圾收集時必須執(zhí)行“Stop the World”這一操作,也就是暫停所有的用戶線程。這就導(dǎo)致對于實(shí)時性要求比較高的系統(tǒng),JVM的垃圾收集可能是一個短板。但是在JDK1.5,Sun提供了CMS(Concurrent Mark and Sweep)垃圾收集器,通過GC線程和用戶線程并發(fā)執(zhí)行減少GC時間,提高了JVM的實(shí)時性。在JVM的各種應(yīng)用中,gc調(diào)優(yōu)是一個關(guān)鍵的部分,主要目標(biāo)是減少GC的次數(shù)并且降低每次GC的時間。關(guān)于這部分內(nèi)容,后續(xù)的JVM內(nèi)存管理會詳細(xì)討論。
在命令行執(zhí)行”java Main”就會開啟一個JVM實(shí)例,我們可以通過jps,jstat等JVM工具觀察JVM的運(yùn)行狀態(tài),下面以運(yùn)行com.ntes.money.Main這個類為例來描述一下JVM執(zhí)行一個程序的流程。
當(dāng)在命令行執(zhí)行”java -Xmx=12m -Xms=12m -Dname=value com.ntes.money.Main”這個命令時,JVM的執(zhí)行流程是:
1)加載JVM,主要是加載動態(tài)鏈接庫,windows下是jvm.dll,Linux下是libjvm.so;
2)設(shè)置JVM啟動參數(shù),比如命令中的-Xmx=12m -Xms=12m用于設(shè)置堆大小。
3)初始化JVM。
4)調(diào)用類加載器子系統(tǒng),加載com.ntes.money.Main。這里給出的是自定義類,根據(jù)類加載器雙親委派鏈,最后是由系統(tǒng)默認(rèn)類加載器(Classpath類加載器)進(jìn)行加載。首先,根據(jù)全路徑類型轉(zhuǎn)化為文件路徑com/ntes/money/Main.class,然后讀取Main.class中的二進(jìn)制信息、解析、加載,在方法區(qū)中形成Main類對應(yīng)的數(shù)據(jù)結(jié)構(gòu)。這里可能拋出ClassNotFoundException,有兩種原因。一是文件路徑com/ntes/money/Main.class不存在;二是com/ntes/money/Main.class文件路徑存在,但是Main.class文件中存儲的不是Main類的信息,比如是Main1,Main2等其他類的信息。這種情況下,會拋出NoClassDefFoundError,然后導(dǎo)致ClassNotFoundException。
5)在方法區(qū)com.ntes.money.Main類對應(yīng)的數(shù)據(jù)結(jié)構(gòu)中,根據(jù)方法描述符及訪問標(biāo)志,查找main方法。這里的描述符,包括了方法的方法名、參數(shù)、返回值,也就是public static void main(String[])。如果找不到對應(yīng)的main方法,會拋出NoSuchMethodError: main異常。
6)通過本地方法(JNI)執(zhí)行main方法。
感謝大家的閱讀,以上就是“如何進(jìn)行JVM體系結(jié)構(gòu)分析”的全部內(nèi)容了,學(xué)會的朋友趕緊操作起來吧。相信創(chuàng)新互聯(lián)小編一定會給大家?guī)砀鼉?yōu)質(zhì)的文章。謝謝大家對創(chuàng)新互聯(lián)網(wǎng)站的支持!
當(dāng)前名稱:如何進(jìn)行JVM體系結(jié)構(gòu)分析
分享網(wǎng)址:http://jinyejixie.com/article12/ggesdc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營銷推廣、域名注冊、品牌網(wǎng)站建設(shè)、網(wǎng)站制作、標(biāo)簽優(yōu)化、自適應(yīng)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)