Java虛擬機底層原理詳細分析
Java虛擬機底層原理
Java虛擬機主要由以下三部分組成。
- 類裝載子系統(tǒng):java代碼編譯成class文件后,首先由類裝載子系統(tǒng)加載到虛擬機內(nèi)存中。
- 運行時數(shù)據(jù)區(qū):就是俗稱的虛擬機內(nèi)存,主要包括我們熟悉的堆、棧、本地方法棧、方法區(qū)(元空間)、程序計數(shù)器
- 字節(jié)碼執(zhí)行引擎:最終java代碼的真正執(zhí)行是由字節(jié)碼執(zhí)行引擎來執(zhí)行的。
虛擬機調(diào)優(yōu)主要針對的是運行時數(shù)據(jù)區(qū),也就是虛擬機內(nèi)存。
一、堆
虛擬機調(diào)優(yōu)主要針對的就是堆。當堆滿了的時候,字節(jié)碼執(zhí)行引擎就會單獨開啟一個垃圾回收線程執(zhí)行g(shù)c。
二、棧
當線程開始執(zhí)行,java虛擬機立刻在棧中為該線程分配一塊內(nèi)存空間,這塊內(nèi)存是這個線程專屬的,主要放線程自己的局部變量,每個線程都有自己的內(nèi)存區(qū)域。
棧由一系列幀組成,一個方法對應(yīng)一塊棧幀內(nèi)存區(qū)域。幀保存一個方法的局部變量、操作數(shù)棧、常量池指針。每一次方法調(diào)用創(chuàng)建一個幀,并壓棧。
虛擬機內(nèi)存中的棧的數(shù)據(jù)結(jié)構(gòu)特性類似數(shù)據(jù)結(jié)構(gòu)中的棧,滿足先進后出(FILO)特性。當一個方法運行完,它對應(yīng)的棧幀就被銷毀。
雖然我們常說棧幀是用來放局部變量的,但實際上要復(fù)雜的多。棧幀內(nèi)部主要包括:局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口四部分。下面以一段簡單的代碼為例,介紹一下這幾部分的作用。
public int compute() { int a = 1; int b = 1; int c = (a + b) * 10; return c; }
java代碼編譯成的class文件打開是十六進制字符無法讀,需要使用javap -c進行反編譯。以上代碼反編譯的結(jié)果如下:
可以通過JVM指令手冊查看每條命令的具體含義。
第0行iconst_1:將int類型常量1壓入操作數(shù)棧。第1行istore_1:將int類型存入局部變量1。這一步就是先在局部變量表分配一塊內(nèi)存給變量a,從操作數(shù)棧中的常量1取出賦給變量a并存入局部變量表。這兩行完成了源碼中的第一行 int a = 1; 。同理,第2行和第3行完成了源碼中的第二行Int b = 1; 。
第4行iload_1:從局部變量1裝載int類型值。這里局部變量1就是a,這一步是把a中的值加載到操作數(shù)棧。第5行iload_2也是同樣道理,從局部變量2也就是b裝載int類型值到操作數(shù)棧。第6行iadd:就是進行加法運算,java虛擬機進行加減乘除運算時,會從操作數(shù)棧的棧頂彈出兩個元素進行運算。這里也就是iload_1和iload_2裝載的值。并且將運算完成的結(jié)果壓回操作數(shù)棧。第7行bipush 10:將一個8位帶符號整數(shù)壓入操作數(shù)棧。這里就是后面的那個10。由于10也要占一個位置,所以這一條指令占了兩個位置,也就沒有了第8行。第9行imul:就是進行乘法運算,與第6行iadd類似。第10行istore_3:將前面運算表達式的結(jié)果從操作數(shù)棧中取出賦給變量c并存入局部變量表。從第4行到第10行完成了源碼中Int c = (a + b) * 10;這一行的操作。
第11行iload_1:從局部變量3裝載int類型值。頁就是把局部變量c中的值取出加載到操作數(shù)棧。第12行ireturn:就很明顯了,就是返回結(jié)果。
通過上面簡單程序的例子,應(yīng)該可以理解局部變量表和操作數(shù)棧的工作方式了吧。操作數(shù)棧簡單的說就是程序在運行過程中,存放操作數(shù)的臨時內(nèi)存。局部變量如果是對象類型,那么對象值實際是存在堆里的,而棧的局部變量表實際上只是保存該對象在堆內(nèi)存中的地址。
棧幀中除了局部變量表和操作數(shù)棧還有動態(tài)連接和方法出口。
每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了方法調(diào)用過程中的動態(tài)連接。Class文件中的常量池中存有大量的符號引用,字節(jié)碼中的方法調(diào)用指令就是以常量池里指向方法的符號引用作為參數(shù)的,這些符號的一部分會在類加載階段或者第一次使用的時候被轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化被稱為靜態(tài)解析。另一部分在每一次運行期間轉(zhuǎn)化為直接引用,被稱為動態(tài)連接。因為方法的執(zhí)行對應(yīng)著棧幀的入棧出棧,所以將所屬方法的引用存放在棧幀中。
方法出口記錄了上級調(diào)用方法的信息,比如compute()方法是main()方法調(diào)用的。當compute()方法執(zhí)行完成返回時,需要返回到上級調(diào)用方法main()繼續(xù)運行,此時需要知道一些調(diào)用方法main()的信息,比如返回到main()的哪個位置。方法出口就記錄了相關(guān)信息。
三、程序計數(shù)器
程序計數(shù)器也是屬于線程的,標識這個線程正在或者馬上要執(zhí)行的那行程序的行號。如上例中0-12就是行號,程序運行過程中,字節(jié)碼執(zhí)行引擎每執(zhí)行一行代碼,都會修改程序計數(shù)器的值。
為什么需要程序計數(shù)器呢?如果當前線程掛起了,讓出了cpu去執(zhí)行其它線程,當線程重新獲得cpu時間片開始執(zhí)行時,需要讓線程知道應(yīng)該執(zhí)行到哪一行了,而程序計數(shù)器則記錄了要執(zhí)行程序的行號。
四、方法區(qū)(元空間)
class文件通過類裝載子系統(tǒng)加載到虛擬機內(nèi)存中,其實主要就是加載到方法區(qū)。方法區(qū)內(nèi)主要是常量、靜態(tài)變量、類元信息等。類元信息就是類的名稱、修飾符、方法簽名、方法中的指令碼等。創(chuàng)建對象時,在對象頭中會保存指向類元信息的地址指針。
JDK1.8之后改叫元空間,也不再使用虛擬機內(nèi)存,而是使用直接內(nèi)存??梢允褂?XX:MetaspaceSize和-XX:MaxMetaspaceSize設(shè)置元空間初始大小以及最大可分配大小。
1.如果不指定元空間的大小,默認情況下,元空間最大的大小是系統(tǒng)內(nèi)存的大小,如果元空間一直擴大,虛擬機可能會消耗完所有的可用系統(tǒng)內(nèi)存。
2.如果元空間內(nèi)存不夠用,就會報OOM。
3.默認情況下,對應(yīng)一個64位的服務(wù)端JVM來說,其默認的-XX:MetaspaceSize值為21MB,這就是初始的高水位線,一旦元空間的大小觸及這個高水位線,就會觸發(fā)Full GC并會卸載沒有用的類,然后高水位線的值將會被重置。
4.從第3點可以知道,如果初始化的高水位線設(shè)置過低,會頻繁的觸發(fā)Full GC,高水位線會被多次調(diào)整。所以為了避免頻繁GC以及調(diào)整高水位線,建議將-XX:MetaspaceSize設(shè)置為較高的值,而-XX:MaxMetaspaceSize不進行設(shè)置。
到此這篇關(guān)于Java虛擬機底層原理詳細分析的文章就介紹到這了,更多相關(guān)Java虛擬機底層原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot整合Mybatis注解開發(fā)的實現(xiàn)代碼
這篇文章主要介紹了SpringBoot整合Mybatis注解開發(fā)的實現(xiàn)代碼,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11Java實現(xiàn)從字符串中找出數(shù)字字符串的方法小結(jié)
這篇文章主要介紹了Java實現(xiàn)從字符串中找出數(shù)字字符串的方法,結(jié)合實例形式總結(jié)分析了Java查找數(shù)字字符串的常用技巧,需要的朋友可以參考下2016-03-03IntelliJ IDEA的build path設(shè)置方法
這篇文章主要介紹了IntelliJ IDEA的build path設(shè)置方法,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04