系列文章目錄
第一章 運(yùn)行區(qū)實(shí)驗(yàn)
前言
JVM(Java虛擬機(jī))運(yùn)行區(qū)是Java程序在運(yùn)行過(guò)程中被JVM所管理的內(nèi)存區(qū)域。它包括了Java程序運(yùn)行時(shí)的堆(Heap)、棧(Stack)、方法區(qū)(Method Area)、本地方法棧(Native Method Stacks)、程序計(jì)數(shù)器和直接內(nèi)存(Direct Memory)等部分。
一、堆(Heap)
堆(Heap)是Java程序運(yùn)行時(shí)用于動(dòng)態(tài)分配內(nèi)存的區(qū)域,也是Java垃圾收集器進(jìn)行垃圾回收的主要區(qū)域。堆內(nèi)存被所有線程共享。
以JDK8為例,堆(Heap)用來(lái)存放對(duì)象,幾乎所有(逃逸分析技術(shù),棧上分配、標(biāo)量替換)對(duì)象都在堆上分配,將Java堆細(xì)分的目的只是為了更好地回收內(nèi)存,或者更快地分配內(nèi)存,堆中沒有內(nèi)存分配對(duì)象,并且無(wú)法擴(kuò)展時(shí),會(huì)拋出OutOfMemoryError異常。
堆(Heap)一般管理新生代/Young區(qū) 、年老代(old區(qū)) 、String字符串常量池。
1.1、新生代/Young區(qū)
新生代里包含Eden區(qū) 與 Survival區(qū)2個(gè)區(qū)。
1.1.1、Eden區(qū)
Eden區(qū)位于Java堆的年輕代,是新對(duì)象分配內(nèi)存的地方,由于堆是所有線程共享的,因此在堆上分配內(nèi)存需要加鎖。而Sun JDK為提升效率,會(huì)為每個(gè)新建的線程在Eden上分配一塊獨(dú)立的空間由該線程獨(dú)享,這塊空間稱為TLAB(Thread Local Allocation Buffer)。在TLAB上分配內(nèi)存不需要加鎖,因此JVM在給線程中的對(duì)象分配內(nèi)存時(shí)會(huì)盡量在TLAB上分配。如果對(duì)象過(guò)大或TLAB用完,則仍然在堆上進(jìn)行分配。如果Eden區(qū)內(nèi)存也用完了,則會(huì)進(jìn)行一次Minor GC(young GC)。
1.1.2、Survival區(qū)
Survival區(qū)與Eden區(qū)相同都在Java堆的年輕代。Survival區(qū)有兩塊,一塊稱為from區(qū),另一塊為to區(qū),這兩個(gè)區(qū)是相對(duì)的,在發(fā)生一次Minor GC后,from區(qū)就會(huì)和to區(qū)互換。在發(fā)生Minor GC時(shí),Eden區(qū)和Survival from區(qū)會(huì)把一些仍然存活的對(duì)象復(fù)制進(jìn)Survival to區(qū),并清除內(nèi)存。Survival to區(qū)會(huì)把一些存活得足夠舊的對(duì)象移至年老代。
1.2、年老代(old區(qū))
年老代里存放的都是存活時(shí)間較久的,大小較大的對(duì)象,因此年老代使用標(biāo)記整理算法。當(dāng)年老代容量滿的時(shí)候,會(huì)觸發(fā)一次Major GC(full GC),回收年老代和年輕代中不再被使用的對(duì)象資源
二、虛擬機(jī)棧(Stack)
每個(gè)線程在創(chuàng)建時(shí)都會(huì)創(chuàng)建一個(gè)私有的JVM棧。每個(gè)方法執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量、操作數(shù)棧、常量池引用等信息。如下圖,棧幀1、棧幀2、棧幀3、棧幀N。
2.1、棧頂緩存技術(shù)
解釋執(zhí)行狀態(tài)下,將訪問最頻繁的數(shù)據(jù)(程序計(jì)數(shù)器、棧頂)緩存在物理CPU的寄存器中,以此降低對(duì)內(nèi)存的讀/寫次數(shù),提升執(zhí)行引擎的執(zhí)行效率。
2.2、溢出
如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許深度,將拋出StackOverflowError異常;如果Java虛擬機(jī)棧容量可以動(dòng)態(tài)擴(kuò)展 ,當(dāng)棧擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存會(huì)拋出OutOfMemoryError異常。
發(fā)生StackOverflowError異常,使用死循環(huán)遞歸代碼可以復(fù)現(xiàn)。
可以使用參數(shù)-Xss選項(xiàng)來(lái)設(shè)置線程的最大??臻g,例如-Xss265k
2.3、棧幀
棧的存儲(chǔ)單位為棧幀,線程上正在執(zhí)行的一個(gè)方法對(duì)應(yīng)一個(gè)棧幀,棧幀是一個(gè)內(nèi)存區(qū)塊,是一個(gè)數(shù)據(jù)集,維系著方法執(zhí)行過(guò)程中的各種數(shù)據(jù)信息。
每個(gè)棧幀包含五部分,分別包括局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈表(指向運(yùn)行時(shí)常量池的方法引用)、方法返回地址和一些額外信息。
2.3.1、局部變量表(local variables)
-
局部變量表定義為一個(gè)數(shù)字?jǐn)?shù)組,主要用于存儲(chǔ)方法參數(shù)和定義在方法體內(nèi)的局部變量,這些數(shù)據(jù)類型包括各種基本數(shù)據(jù)類型、對(duì)象引用(reference),以及returnAddress類型。
-
由于局部變量表是線程的私有數(shù)據(jù),不存在線程的安全問題
-
局部變量表所需容量大小在編譯器確定下來(lái)的,并保存在方法的Code屬性的maximum local variables數(shù)據(jù)項(xiàng)中。在方法運(yùn)行期間是不會(huì)改變局部變量表的大小的。
-
局部變量表的變量只在當(dāng)前方法調(diào)用中有效的,當(dāng)方法調(diào)用結(jié)束后,隨著方法棧幀的消耗,局部變量表也會(huì)隨之銷毀。
-
局部變量表的最基本存儲(chǔ)單位是Slot(變量槽),32位以內(nèi)的類型只占用一個(gè)slot(包括returnAddress類型,引用類型),64位的類型(long和double)占用兩個(gè)slot。
-
每一slot都分配一個(gè)訪問索引,占用兩個(gè)slot的變量,只需要使用前一個(gè)索引即可
-
構(gòu)造方法或者實(shí)例方法(非static),該對(duì)象引用this將會(huì)存放在index為0的slot處
-
局部變量表的變量也是重要的垃圾回收根節(jié)點(diǎn),只要被局部變量表中直接或間接引用的對(duì)象都不會(huì)被回收。
2.3.2、操作數(shù)棧(Operand Stack LIFO)
- 編譯后 .Class文件的Code屬性的max_stacks數(shù)據(jù)記錄操作數(shù)棧的最大深度
- 操作數(shù)棧,在方法執(zhí)行過(guò)程中,根據(jù)字節(jié)碼指令,往棧中寫入數(shù)據(jù)或提取數(shù)據(jù),即入棧或者出棧,使用數(shù)組實(shí)現(xiàn)
- 操作數(shù)棧,主要用于保存計(jì)算過(guò)程的中間結(jié)果,同時(shí)作為計(jì)算過(guò)程中變量臨時(shí)的存儲(chǔ)空間。
2.3.3、動(dòng)態(tài)鏈接
運(yùn)行期間把常量池中的符號(hào)引用轉(zhuǎn)換成直接引用的過(guò)程,叫做動(dòng)態(tài)鏈接;
每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)鏈接。
2.3.4、方法返回地址
方法返回地址存儲(chǔ)的是調(diào)用該方法的pc寄存器的值。方法正常退出時(shí),調(diào)用者的pc計(jì)數(shù)器的值作為返回地址,即調(diào)用該方法的指令的下一條指令的地址。
2.3.5、額外信息
Java虛擬機(jī)規(guī)范,允許虛擬機(jī)實(shí)現(xiàn),在棧幀之中增加一些規(guī)范里沒有描述的信息,例如與調(diào)試、性能收集相關(guān)的信息。
三、方法區(qū)(MetaData Space)
方法區(qū)(MetaData Space 元空間)/Non-Heap,用于存儲(chǔ)已被JVM加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
3.1、方法區(qū)和永久代的關(guān)系
在JDK1.8之前,習(xí)慣把方法區(qū)稱為“永久代”。HotSpot 虛擬機(jī)在1.7之前使用永久代來(lái)管理(實(shí)現(xiàn))方法區(qū),使得jvm的垃圾回收器能像管理堆內(nèi)存一樣來(lái)管理永久代這部分內(nèi)存,而不用專門為方法區(qū)編寫內(nèi)存管理代碼。但這種設(shè)計(jì)使得虛擬機(jī)更容易出現(xiàn)內(nèi)存溢出問題,而不像J9和JRockit只要沒有觸碰到進(jìn)程可用內(nèi)存的上限(32位4GB),就沒有問題。
JDK1.7把原本放在永久代的字符串常量池、靜態(tài)變量等移出(主要剩余類型信息)
JDK1.8完全廢棄了永久代的概念,改用與JRockit、J9一樣,在本地內(nèi)存中實(shí)現(xiàn)的元空間方法區(qū)無(wú)法滿足新的內(nèi)存分配需求時(shí),將拋出OutOfMemoryError異常。
四、本地方法棧(Native Method Stacks)
Native Method Stacks 為虛擬機(jī)使用到的本地(Native)方法服務(wù),本地方法棧也會(huì)在棧深度溢出或者棧擴(kuò)展失敗時(shí)分別拋出StackOverflowError和OutOfMemoryError異常。
五、程序計(jì)數(shù)器
程序計(jì)數(shù)器,當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器,字節(jié)碼解釋器工作時(shí),通過(guò)改變計(jì)數(shù)器的值,執(zhí)行下一條指令,此內(nèi)存是JVM規(guī)范中唯一一個(gè)沒有OutOfMemoryError的區(qū)域。
六、直接內(nèi)存(Direct Memory)
DM并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是《Java虛擬機(jī)規(guī)范》中定義的內(nèi)存區(qū)域。但是這部分內(nèi)存也被頻繁地使用,而且也可能導(dǎo)致OutOfMemoryError異常出現(xiàn),例如:NIO 中的 DirectByteBuffer 直接操作堆外內(nèi)存,避免在Java堆和Native堆中來(lái)回復(fù)制數(shù)據(jù)。
同時(shí)也可以關(guān)注零拷貝(DMA)技術(shù)的應(yīng)用。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-712606.html
總結(jié)
這些內(nèi)存區(qū)域的管理和優(yōu)化對(duì)于Java程序的性能和可靠性至關(guān)重要。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-712606.html
到了這里,關(guān)于JVM系列 運(yùn)行時(shí)數(shù)據(jù)區(qū)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!