JVM基礎(chǔ)及內(nèi)存區(qū)域
JVM基礎(chǔ)知識(shí)
首先要了解JVM的基礎(chǔ)知識(shí),知道JVM在Java中起到了什么作用?以及JVM的一些概念。
Java從編譯到執(zhí)行
java從編譯到執(zhí)行:java文件通過javac編譯成class文件,通過JVM中ClassLoader類加載器執(zhí)行class文件,一般會(huì)字節(jié)碼解析器執(zhí)行也可能會(huì)通過JIT編譯器執(zhí)行,通過執(zhí)行引擎編譯成機(jī)器碼,由硬件處理。Java文件 -> 編譯器 -> 字節(jié)碼 -> JVM -> 機(jī)器碼
JDK、JVM、JRE區(qū)別
- JVM:JVM只是一個(gè)翻譯,把class文件翻譯成機(jī)器碼,JVM不會(huì)自己生成代碼,需要自己編寫代碼,同時(shí)還需要很多依賴類庫,這時(shí)就需要用到JRE了。
- JRE:JRE除了包含JVM之外,還提供了很多類庫,也就是很多jar包,它提供了一些即插即用的功能比如文件操作、連接網(wǎng)絡(luò)、I/O操作等,這些都是JRE提供的基礎(chǔ)類庫。JVM標(biāo)準(zhǔn)加上實(shí)現(xiàn)的一大堆基礎(chǔ)類庫,就組成了Java的運(yùn)行時(shí)環(huán)境(Java Runtime Environment)JRE
- JDK:對(duì)于服務(wù)器可能只需要JRE就可以了,但是對(duì)于程序員只有JRE還不夠,程序員要寫完代碼、編譯代碼、調(diào)試代碼、打包代碼,甚至有時(shí)候還需要反編譯代碼,這時(shí)候就需要使用JDK,JDK包含了:javac(編譯代碼)、java、jar(打包代碼)、javap(反編譯)。
JVM的跨平臺(tái)性與語言無關(guān)性
JVM 不識(shí)別.java文件而是識(shí)別.class文件字節(jié)碼,所以JVM和語言是解耦的,可以編譯成class的語言都可以在JVM上運(yùn)行。JVM只識(shí)別字節(jié)碼,JVM和語言解耦的,沒有直接關(guān)聯(lián),所以JVM是和語言無關(guān)的。注意JVM運(yùn)行不是翻譯Java文件,而是識(shí)別class文件,比如Scala、kotlin、groovy它們都可以編譯成字節(jié)碼文件,所以可以在JVM上跑,這也就是說JVM是跨平臺(tái)的。
JVM的跨平臺(tái)性和語言無關(guān)性,如下圖所示:編譯成字節(jié)碼交給JVM處理為機(jī)器碼,在設(shè)備上運(yùn)行
常見的JVM實(shí)現(xiàn)
image.png- Hotspot: 目前使用最多的虛擬機(jī),可以通過執(zhí)行
java --version查看你現(xiàn)在使用虛擬機(jī)的名字
image.png- J9: IBM 有自己的 java 虛擬機(jī)實(shí)現(xiàn),它的名字叫做 J9. 主要是用在 IBM 產(chǎn)品(IBM WebSphere 和 IBM 的 AIX 平臺(tái)上)
- TaobaoVM只有一定體量、一定規(guī)模的廠商才會(huì)開發(fā)自己的虛擬機(jī),比如淘寶有自己的 VM,它實(shí)際上是 Hotspot 的定制版,專門為淘寶準(zhǔn)備的,阿里、天 貓都是用的這款虛擬機(jī)。
- LiquidVM它是一個(gè)針對(duì)硬件的虛擬機(jī),它下面是沒有操作系統(tǒng)的(不是 Linux 也不是 windows),下面直接就是硬件,運(yùn)行效率比較高。
- zing它屬于 zual 這家公司,非常牛,是一個(gè)商業(yè)產(chǎn)品,很貴!它的垃圾回收速度非常快(1 毫秒之內(nèi)),是業(yè)界標(biāo)桿。它的一個(gè)垃圾回收的算法后來被Hotspot 吸收才有了現(xiàn)在的 ZGC。
JVM的知識(shí)模塊
JVM有非常龐大的知識(shí)體系:比如內(nèi)存結(jié)構(gòu)、垃圾回收、類加載、執(zhí)行引擎、類文件結(jié)構(gòu)、監(jiān)控工具、性能調(diào)優(yōu)等等。學(xué)習(xí)JVM要把握重點(diǎn),在JVM的所有知識(shí)體系中或多或少都和內(nèi)存結(jié)構(gòu)有關(guān),比如垃圾回收回收的就是內(nèi)存、類加載加載到的地方也是內(nèi)存、性能優(yōu)化也涉及內(nèi)存、執(zhí)行引擎和內(nèi)存密不可分、類文件結(jié)構(gòu)和內(nèi)存設(shè)計(jì)有關(guān)、監(jiān)控工具也會(huì)監(jiān)控內(nèi)存,JVM也是一個(gè)虛擬化的操作系統(tǒng),處理虛擬指令外,還需要虛擬化的內(nèi)存,而這個(gè)虛擬化的內(nèi)存就是JVM的內(nèi)存區(qū)域。學(xué)習(xí)JVM首先從內(nèi)存結(jié)構(gòu)開始。
JVM的內(nèi)存區(qū)域
jvm內(nèi)存區(qū)域圖:
JVM 是Java虛擬機(jī),類似一個(gè)操作系統(tǒng),class就是指令,比如一個(gè)操作系統(tǒng)有8G的內(nèi)存,其中3G為虛擬內(nèi)存(運(yùn)行時(shí)數(shù)據(jù)區(qū))剩下的5G可以理解為JVM的直接內(nèi)存,這個(gè)虛擬內(nèi)存就是JVM的運(yùn)行時(shí)數(shù)據(jù)區(qū)域,另外還有一個(gè)直接內(nèi)存不是運(yùn)行時(shí)數(shù)據(jù)區(qū)域的一部分,但是會(huì)頻繁使用。
運(yùn)行時(shí)數(shù)據(jù)區(qū)域
運(yùn)行時(shí)數(shù)據(jù)區(qū)域:Java虛擬機(jī)在執(zhí)行Java程序的過程中會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域
- 線程私有區(qū)域
- 虛擬機(jī)棧
- 本地方法棧
- 程序計(jì)數(shù)器
- 線程共享區(qū)
- 運(yùn)行時(shí)常量池
- 方法區(qū)
- 堆
虛擬機(jī)棧
存儲(chǔ)當(dāng)前線程運(yùn)行Java方法所需的數(shù)據(jù),指令、返回地址
虛擬機(jī)棧中主要包括棧幀,而棧幀包括:局部變量、操作數(shù)棧、動(dòng)態(tài)鏈接、完成出口 在實(shí)際代碼中,一個(gè)線程是可以運(yùn)行多個(gè)方法的。如下代碼:main -> A -> B -> C, 運(yùn)行代碼,線程1來運(yùn)行,就會(huì)有一個(gè)對(duì)應(yīng)的虛擬機(jī)棧,同時(shí)在執(zhí)行每個(gè)方法的時(shí)候都會(huì)打包成一個(gè)棧幀。
/**
?*?虛擬機(jī)棧
?*/
public?class?MouthedOrStack?{
????public?static?void?main(String[]?args)?{
????????A();
????}
????private?static?void?A()?{
????????B();
????}
????private?static?void?B()?{
????????C();
????}
????private?static?void?C()?{
????}
}
代碼的執(zhí)行過程:如下圖所示,執(zhí)行main()方法的時(shí)候入棧,執(zhí)行A()方法的時(shí)候入棧.... 當(dāng)C()方法執(zhí)行完了,C()方法出棧,接著B方法運(yùn)行完了出棧,A方法運(yùn)行完了出棧,最后main方法執(zhí)行完了出棧。這個(gè)就是Java方法運(yùn)行對(duì)虛擬機(jī)棧的一個(gè)影響。虛擬機(jī)棧就是用來存儲(chǔ)線程運(yùn)行方法中的數(shù)據(jù)的,每一個(gè)方法對(duì)應(yīng)一個(gè)棧幀
:::tips
虛擬機(jī)棧是基于線程的,哪怕只有一個(gè)main()方法,也是以線程的方式運(yùn)行的,在線程的生命周期中,參與計(jì)算的數(shù)據(jù)會(huì)頻繁地入棧和出棧,棧的生命周期和線程一樣的
:::
棧的大小限制:-Xss 設(shè)置線程堆棧大小,不同的操作系統(tǒng),不同的位數(shù)虛擬機(jī)棧的大小是不同的。查看jvm的設(shè)置
-Xsssize
Sets?the?thread?stack?size?(in?bytes).?Append?the?letter?k?or?K?to?indicate?KB,?m?or?M?to?indicate?MB,?g?or?G?to?indicate?GB.?The?default?value?depends?on?the?platform:
Linux/ARM?(32-bit):?320?KB
Linux/i386?(32-bit):?320?KB
Linux/x64?(64-bit):?1024?KB
OS?X?(64-bit):?1024?KB
Oracle?Solaris/i386?(32-bit):?320?KB
Oracle?Solaris/x64?(64-bit):?1024?KB
The?following?examples?set?the?thread?stack?size?to?1024?KB?in?different?units:
-Xss1m
-Xss1024k
-Xss1048576
This?option?is?equivalent?to?-XX:ThreadStackSize.
虛擬機(jī)棧常見的錯(cuò)誤:棧溢出:StackOverflowError ? 常見的場景:一般無限循環(huán)遞歸會(huì)造成這個(gè)錯(cuò)誤 只有壓棧沒有彈出棧,虛擬機(jī)棧內(nèi)存也不是無限大的,它是有大小限制的如下代碼:
/**
?*?棧異常
?*/
public?class?StackError?{
????public?static?void?main(String[]?args)?{
????????A();
????}
????public?static?void?A()?{
????????A();
????}
}
虛擬機(jī)棧存儲(chǔ)的主要是棧幀
什么是棧幀 (重點(diǎn)):::tips
在每個(gè)Java方法被調(diào)用的時(shí)候,都會(huì)創(chuàng)建一個(gè)棧幀,并入棧。一旦方法完成響應(yīng)的調(diào)用,則出棧
:::如下圖:
根據(jù)上述的講解,我們都知道虛擬機(jī)棧主要是存儲(chǔ)當(dāng)前線程運(yùn)行Java方法中的指令、數(shù)據(jù)、返回地址如上圖所示,那么每一個(gè)方法都是一個(gè)棧幀,而棧幀中存儲(chǔ)著方法中的變量數(shù)據(jù)、指令、返回等
棧幀主要包括:局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、返回地址。根據(jù)如下的代碼,來模擬一個(gè)方法調(diào)用后再棧幀中的處理過程
public?class?OperandStack?{
????public?int?test()?{
????????int?a?=?0;
????????int?b?=?1;
????????int?z?=?(a?+?b)?*?10;
????????return?z;
????}
????public?static?void?main(String[]?args)?{
????????OperandStack?operandStack?=?new?OperandStack();
????????operandStack.test();
????}
}
下面我們來模擬一下上述代碼中的test方法是如何在虛擬機(jī)棧中運(yùn)行的。如下圖是一個(gè),默認(rèn)狀態(tài)的虛擬機(jī)棧,假設(shè)test()在線程1中執(zhí)行,可以看到局部變量表中有一個(gè)this這個(gè)是默認(rèn)的指向的當(dāng)前的對(duì)象。首先,我們需要有一個(gè)正確的認(rèn)知,既然JVM是Java的一個(gè)虛擬機(jī),那么JVM就具備一個(gè)操作系統(tǒng)所具備的核心功能:CPU + 主內(nèi)存 + 緩存,那么JVM是一個(gè)模擬版的操作系統(tǒng),JVM執(zhí)行引擎(CPU) + 棧、堆等(主內(nèi)存) + 操作數(shù)棧(緩存),首先要知道一個(gè)操作系統(tǒng)是如何執(zhí)行的,比如執(zhí)行1+1計(jì)算,那么這個(gè)計(jì)算是在CPU中執(zhí)行,直接結(jié)果放到緩存中的,那么JVM也是同樣的原理,JVM是通過執(zhí)行引擎計(jì)算,將計(jì)算結(jié)果存入操作數(shù)棧中。理解這個(gè)原理我們就可以很輕松的理解虛擬機(jī)棧的運(yùn)行過程。
我們都知道JVM是處理class文件中的指令,那么我們需要把上述的Java源代碼,編譯成class文件,然后通過javap -c指令反匯編,來查看class文件中的指令
如下代碼就是編譯后的class字節(jié)碼文件
public?class?course01.OperandStack?{
??public?course01.OperandStack();
????Code:
???????0:?aload_0
???????1:?invokespecial?#1??????????????????//?Method?java/lang/Object."<init>":()V
???????4:?return
??public?int?test();
????Code:
???????0:?iconst_0
???????1:?istore_1
???????2:?iconst_1
???????3:?istore_2
???????4:?iload_1
???????5:?iload_2
???????6:?iadd
???????7:?bipush????????10
???????9:?imul
??????10:?istore_3
??????11:?iload_3
??????12:?ireturn
??public?static?void?main(java.lang.String[]);
????Code:
???????0:?new???????????#2??????????????????//?class?course01/OperandStack
???????3:?dup
???????4:?invokespecial?#3??????????????????//?Method?"<init>":()V
???????7:?astore_1
???????8:?aload_1
???????9:?invokevirtual?#4??????????????????//?Method?test:()I
??????12:?pop
??????13:?return
}
這里面涉及到了一些指令,這些指令不需要死記硬背,而是需要去理解,可以根據(jù)我提供的鏈接,直接查找這個(gè)指令的意思即可。首先執(zhí)行:int a = 0;在字節(jié)碼的指令中首先看到的是:iconst_0 這個(gè)指令是什么意思呢?直接從上述我提供的鏈接中查找, 意思就是將一個(gè)常量0加載到操作數(shù)棧,哦原來這個(gè)意思就是將int a = 0的常量值放到操作數(shù)棧中,這時(shí)候線程1中的虛擬機(jī)棧就變成了如下:操作數(shù)棧中多了一個(gè)常量0
我們繼續(xù)往下走,一般指令都是按照順序執(zhí)行的,指令是不會(huì)跳躍執(zhí)行,所以我們繼續(xù)順著指令,下一個(gè)指令執(zhí)行的是:istore_1 查找istore指令是什么意思:講一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表中,哦原來是這個(gè)意思,就是從操作數(shù)棧的棧頂取值然后放到局部變量表中,不知道大家有沒有注意一個(gè)問題istore_1 為什么是1而不是0呢?這是因?yàn)榫植孔兞勘碇性?的位置默認(rèn)有一個(gè)this指向當(dāng)前的對(duì)象。(理解每一個(gè)步驟及參數(shù)的意義是很重要的)
這是JVM執(zhí)行istore_1這個(gè)指令,此時(shí)虛擬機(jī)棧中的情況:局部變量表的1的位置多了一個(gè)數(shù)值0的常量,此時(shí)操作數(shù)棧是沒有數(shù)據(jù)的。因?yàn)椴僮鲾?shù)棧的數(shù)據(jù)出棧存入到局部變量表中了。
那么剩下的兩個(gè)指令執(zhí)行,我相信就不用在細(xì)說了吧
?2: iconst_1 將常量1存入操作數(shù)棧中3: istore_2 將操作數(shù)棧的數(shù)據(jù)存入到局部變量表中
此時(shí),虛擬機(jī)棧中的情況:
局部變量表中存儲(chǔ)著:this、0、1,操作數(shù)棧是空的,我們需要理解操作數(shù)棧其實(shí)就是緩存,主要用來臨時(shí)存儲(chǔ)計(jì)算結(jié)果的我們不可能在緩存中長久保存數(shù)據(jù)。(如果還不理解建議學(xué)習(xí)一下操作系統(tǒng)的基礎(chǔ))
OK,繼續(xù)執(zhí)行指令:iload指令,這個(gè)指令是將一個(gè)局部變量加載到操作數(shù)棧iload_1 :將局部變量表中1的位置存儲(chǔ)的數(shù)據(jù),加載到操作數(shù)棧中iload_2 :將局部變量表中2的位置存儲(chǔ)的數(shù)據(jù),加載到操作數(shù)棧中
思考:為什么又要加載到操作數(shù)棧呢?例如CPU要計(jì)算數(shù)據(jù),需要從緩存中拿取數(shù)據(jù)計(jì)算,然后將計(jì)算結(jié)果存入到緩存中。JVM的操作也是同樣的原理
此時(shí),虛擬機(jī)棧中的運(yùn)行情況:
繼續(xù)執(zhí)行指令:iadd : 算法指令 用于對(duì)兩個(gè)操作數(shù)棧上的數(shù)值進(jìn)行某種特定的運(yùn)算,并把結(jié)果重新存入到操作數(shù)棧頂
此時(shí)虛擬機(jī)棧的運(yùn)行情況:首先從操作數(shù)棧取出兩個(gè)數(shù)據(jù),由執(zhí)行引擎進(jìn)行計(jì)算:(a + b) 得到的結(jié)果1存入到操作數(shù)棧的棧頂
繼續(xù)執(zhí)行指令:bipush ? ? ? ?10 :把一個(gè)數(shù)值推送到操作數(shù)棧棧頂,這里其實(shí)執(zhí)行的代碼就相當(dāng)于:(a+b)*10 bipush指令直接將數(shù)值10推送到操作數(shù)棧的棧頂中。
然后執(zhí)行指令:imul :運(yùn)算指令,對(duì)操作數(shù)棧中的兩個(gè)數(shù)據(jù)進(jìn)行乘法運(yùn)算
那么,此時(shí)虛擬機(jī)棧中的運(yùn)行情況:10和1進(jìn)行乘法運(yùn)算,得到結(jié)果10存入到操作數(shù)棧中
繼續(xù)執(zhí)行指令:istore_3 將操作數(shù)棧存儲(chǔ)到局部變量表中,此時(shí)虛擬機(jī)棧的運(yùn)行情況如下:
繼續(xù)執(zhí)行指令:iload_3 :將局部變量表3的數(shù)據(jù),加載到操作數(shù)棧中ireturn :將操作數(shù)棧中的數(shù)據(jù)取出,然后壓人調(diào)用者的棧幀的操作數(shù)棧中
此時(shí)test()方法執(zhí)行完畢,會(huì)從線程1 的虛擬機(jī)棧中出棧,釋放
那么此時(shí)虛擬機(jī)棧的運(yùn)行情況:此時(shí)虛擬機(jī)棧中,只剩下main棧幀,而test()方法已經(jīng)執(zhí)行完畢了出棧了,需要注意的是main棧幀的操作數(shù)棧中有一個(gè)常量10這個(gè)常量10就是test棧幀執(zhí)行ireturn 指令,壓入到main棧幀的操作數(shù)棧中的。
main方法執(zhí)行完畢從虛擬機(jī)棧中出棧,OK那么此時(shí),我們的整個(gè)代碼就執(zhí)行結(jié)束了。其實(shí)整個(gè)過程就是虛擬機(jī)棧的執(zhí)行的過程,相信大家都理解了虛擬機(jī)棧的作用以及運(yùn)行過程了,嗯....可以吊打面試官了。
局部變量表:變量和引用變量 :::tips 局部變量表,用于存放局部變量就是方法中的變量,首先它是一個(gè)32位的長度,主要存放Java的八大基礎(chǔ)數(shù)據(jù)類型,如果是64位的就使用高低位占用兩個(gè)也可以存放下,如果是局部的一些對(duì)象,只需要存放它的一個(gè)引用地址即可。默認(rèn)會(huì)有一個(gè)this當(dāng)前類的對(duì)象的引用 :::
操作數(shù)棧
操作數(shù)棧存放Java方法的操作數(shù)的,它就是一個(gè)棧結(jié)構(gòu)先進(jìn)后出。操作數(shù)棧就是用來操作,操作的元素可以是任意的Java數(shù)據(jù)類型,所以當(dāng)一個(gè)方法剛剛開始的時(shí)候,這個(gè)方法的操作數(shù)棧就是空的。操作數(shù)棧本質(zhì)上是JVM執(zhí)行引擎的一個(gè)工作區(qū),也就是所方法在執(zhí)行,才會(huì)對(duì)操作數(shù)棧進(jìn)行操作,如果代碼不執(zhí)行,操作數(shù)棧其實(shí)就是空的。一般操作系統(tǒng):需要有這些東西CPU + 主內(nèi)存 + 緩存 :::tips JVM是一個(gè)模擬版的操作系統(tǒng),JVM執(zhí)行引擎(CPU) + 棧、堆等(主內(nèi)存) + 操作數(shù)棧(緩存) ::: 指令:都是有執(zhí)行引擎來處理的
ICONST_0(iconst_<n>)?:?將一個(gè)常量0(n)壓入操作數(shù)棧
ISTORE 1 (istore_<n>):表示將操作數(shù)棧存入到局部變量表?下標(biāo)為1(n)的位置
ILOAD?1?:加載存儲(chǔ)指令?將局部變量下標(biāo)為1的值?加載到操作數(shù)棧
ILOAD?2?:加載存儲(chǔ)指令?將局部變量下標(biāo)為2的值?加載到操作數(shù)棧
IADD?:?算法指令?兩條數(shù)據(jù)從操作數(shù)棧出棧相加(執(zhí)行引擎進(jìn)行計(jì)算)?,運(yùn)算后的結(jié)果入到操作數(shù)棧(why?)?而執(zhí)行引擎相當(dāng)于CPU不做數(shù)據(jù)的存儲(chǔ),操作數(shù)棧相當(dāng)于緩存,可以存儲(chǔ)中間數(shù)據(jù)
BIPUSH?10?:?將10常量壓入操作數(shù)棧
IMUL :?算法指令?乘法。?操作數(shù)棧出棧,執(zhí)行引擎計(jì)算得到的結(jié)果,存入操作數(shù)棧
ISTORE 3 :操作數(shù)棧出棧,存入到局部變量表下標(biāo)為3的位置
ILOAD?3?:?將局部變量下標(biāo)為3的值?加載到操作數(shù)棧
IRETURN :?方法的返回指令:?因?yàn)閳?zhí)行引擎都是處理操作數(shù)棧中的數(shù)據(jù)
- 動(dòng)態(tài)鏈接
Java語言的特性多態(tài),具體的會(huì)在后面的章節(jié)中單獨(dú)講解。
- 完成出口 返回地址
正常返回(調(diào)用程序計(jì)數(shù)器中的地址作為返回)、異常的話(通過異常處理器表<非棧幀中的>來確定)
程序計(jì)數(shù)器
指向當(dāng)前線程正在執(zhí)行的字節(jié)碼的指令地址
注意
:::tips
程序計(jì)數(shù)器是唯一不會(huì)發(fā)生OOM的內(nèi)存溢出
:::
其實(shí)在講虛擬機(jī)棧運(yùn)行過程的時(shí)候,圖中有一個(gè)程序計(jì)數(shù)器。程序計(jì)數(shù)器是一塊很小的內(nèi)存空間,主要用來記錄各個(gè)線程執(zhí)行的字節(jié)碼的地址,例如:分支、循環(huán)、跳轉(zhuǎn)、異常、線程恢復(fù)等都依賴于計(jì)數(shù)器。為什么要有程序計(jì)數(shù)器這個(gè)東西呢?JVM中的程序計(jì)數(shù)器 映射了操作系統(tǒng):CPU時(shí)間片輪轉(zhuǎn)機(jī)制。由于Java是多線程語言,當(dāng)執(zhí)行的線程數(shù)量超過CPU核數(shù)時(shí),線程之間會(huì)根據(jù)時(shí)間片輪詢爭奪CPU資源。如果一個(gè)線程的時(shí)間片用完了,或者是其他原因?qū)е逻@個(gè)線程的CPU資源被提前搶奪,那么這個(gè)退出的線程就需要單獨(dú)一個(gè)程序計(jì)數(shù)器,來記錄下一條的運(yùn)行的指令.
如下圖,需要程序計(jì)數(shù)器來記錄運(yùn)行的指令。
因?yàn)?JVM 是虛擬機(jī),內(nèi)部有完整的指令與執(zhí)行的一套流程,所以在運(yùn)行 Java 方法的時(shí)候需要使用程序計(jì)數(shù)器(記錄字節(jié)碼執(zhí)行的地址或行號(hào)),如 果是遇到本地方法(native 方法),這個(gè)方法不是 JVM 來具體執(zhí)行,所以程序計(jì)數(shù)器不需要記錄了,這個(gè)是因?yàn)樵诓僮飨到y(tǒng)層面也有一個(gè)程序計(jì)數(shù)器,
這個(gè)會(huì)記錄本地代碼的執(zhí)行的地址,所以在執(zhí)行 native 方法時(shí),JVM 中程序計(jì)數(shù)器的值為空(Undefined)。
本地方法棧
執(zhí)行的是native關(guān)鍵字的方法,native的關(guān)鍵字的方法是在C/C++中實(shí)現(xiàn)的,例如hashcode()方法,有一個(gè)動(dòng)態(tài)鏈接hashcode.dll,本地方法并不是Java實(shí)現(xiàn)的。為什么會(huì)有本地方法棧的原因是:虛擬機(jī)規(guī)范中規(guī)定的。(后續(xù)版本中虛擬機(jī)棧和本地方法棧合二為一了)
為什么Java要用native方法呢?歷史的一些原因,Java實(shí)現(xiàn)不了的用c/c++去實(shí)現(xiàn)
本地方法棧和虛擬機(jī)棧是非常相似的一個(gè)區(qū)域,只不過本地方法棧服務(wù)的對(duì)象時(shí)native方法。
方法區(qū)
方法區(qū)與堆空間類似,也是一個(gè)共享內(nèi)存的區(qū)域,方法區(qū)是線程共享的,加入兩個(gè)線程都視圖訪問方法區(qū)中的同一個(gè)類信息,而這個(gè)類還沒有裝入JVM,那么此時(shí)就只允許一個(gè)線程加載它,另一個(gè)線程必須等待。在HotSpot虛擬機(jī)、Java7版本中已經(jīng)將永久代的靜態(tài)變量和運(yùn)行時(shí)常量池轉(zhuǎn)移到堆中了,其余部分則存儲(chǔ)在JVM的非堆內(nèi)存中,而Java8版本已經(jīng)將方法區(qū)中實(shí)現(xiàn)的永久代去掉了,并用元空間代替了之前的永久代,并且元空間的存儲(chǔ)位置是本地內(nèi)存。邏輯劃分
JDK1.7 永久代、JDK1.8 元空間
放class文件,解析,放到常量池 運(yùn)行時(shí)常量池(Runtime Constant Pool)是每一個(gè)類或接口的常量池的運(yùn)行時(shí)表示形式,它包括了若干種不同的常量:從編譯期可知的數(shù)值字面量到必須運(yùn)行期解析后才能獲得的方法或字段引用。運(yùn)行時(shí)常量池是方法區(qū)的一部分。運(yùn)行時(shí)常量池相對(duì)于Class常量池的另外一個(gè)重要特征具備動(dòng)態(tài)性,我會(huì)在后期文章中詳細(xì)講解常量池 運(yùn)行時(shí)常量池:
- class文件加載到常量池--- 符號(hào)引用替換為直接引用 例如:person類引用了Tool工具類 內(nèi)存地址(直接引用)
堆
堆是JVM上最大的內(nèi)存區(qū)域,我們申請(qǐng)的幾乎所有的對(duì)象,都是在這里存儲(chǔ)的。包括常說的垃圾回收、操作的對(duì)象就是堆。堆空間一般是程序啟動(dòng)時(shí),就申請(qǐng)了,但是并不一定會(huì)全部使用。堆一般設(shè)置成可伸縮的 隨著對(duì)象的頻繁創(chuàng)建,堆空間占用的越來越多,就需要不定期的對(duì)不再使用的對(duì)象進(jìn)行回收,這個(gè)過程就叫做GC(Garbage Collection) 對(duì)于普通對(duì)象,JVM首先會(huì)在堆上創(chuàng)建對(duì)象,然后在其他使用的地方使用它的引用,例如,把這個(gè)引用保存在虛擬機(jī)棧的局部變量表中。對(duì)于基本數(shù)據(jù)類型來說,有兩種情況:
- 在方法體內(nèi)聲明的基本數(shù)據(jù)類型的對(duì)象,他就會(huì)在棧上直接分配。
- 其他情況都是在堆上分配的。
堆大小參數(shù): -Xms:堆的最小值; -Xmx:堆的最大值; -Xmn:新生代的大小; -XX:NewSize;新生代最小值; -XX:MaxNewSize:新生代最大值;
直接內(nèi)存
直接內(nèi)存不屬于運(yùn)行時(shí)數(shù)據(jù)區(qū)域,例如給JVM分配了8G內(nèi)存,虛擬內(nèi)存3G,剩下的5G就是直接內(nèi)存也可以成為堆外內(nèi)存。
直接內(nèi)存有一種更加科學(xué)的叫法,堆外內(nèi)存。JVM 在運(yùn)行時(shí),會(huì)從操作系統(tǒng)申請(qǐng)大塊的堆內(nèi)存,進(jìn)行數(shù)據(jù)的存儲(chǔ);同時(shí)還有虛擬機(jī)棧、本地方法棧和程序計(jì)數(shù)器,這塊稱之為棧區(qū)。操作系統(tǒng)剩余的 內(nèi)存也就是堆外內(nèi)存。
它不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是 java 虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域;如果使用了 NIO,這塊區(qū)域會(huì)被頻繁使用,在 java 堆內(nèi)可以用directByteBuffer 對(duì)象直接引用并操作;
這塊內(nèi)存不受 java 堆大小限制,但受本機(jī)總內(nèi)存的限制,可以通過-XX:MaxDirectMemorySize 來設(shè)置(默認(rèn)與堆內(nèi)存最大值一樣),所以也會(huì)出現(xiàn) OOM 異 常。
小結(jié): 1、直接內(nèi)存主要是通過 DirectByteBuffer 申請(qǐng)的內(nèi)存,可以使用參數(shù)“MaxDirectMemorySize”來限制它的大小。
2、其他堆外內(nèi)存,主要是指使用了 Unsafe 或者其他 JNI 手段直接直接申請(qǐng)的內(nèi)存。堆外內(nèi)存的泄漏是非常嚴(yán)重的,它的排查難度高、影響大,甚至?xí)斐芍鳈C(jī)的死亡。后續(xù)章節(jié)會(huì)詳細(xì)講。
同時(shí),要注意 Oracle 之前計(jì)劃在 Java 9 中去掉 sun.misc.Unsafe API。這里刪除 sun.misc.Unsafe 的原因之一是使 Java 更加安全,并且有替代方案。目前我們主要針對(duì)的 JDK1.8,JDK1.9 暫時(shí)不放入討論范圍中。 EHcache、消息中間件大量的使用直接內(nèi)存。mac 啟動(dòng)HSDBsudo java -cp /Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/sa-jdi.jar sun.jvm.hotspot.HSDB 啟動(dòng)HSDB工具
