JVM 執(zhí)行 Java 程序時(shí)的內(nèi)存區(qū)域劃分

JVM Memory Layout

在學(xué)習(xí) Java 虛擬機(jī)(后面簡(jiǎn)稱:JVM)中的垃圾回收機(jī)制(GC)之前,先需要了解 在 JVM 中的 Java 程序(class 文件)加載到內(nèi)存之后到底是怎么存的。在閱讀了JVM規(guī)范和周志明的《深入理解Java虛擬機(jī)(第2版)》之后,總結(jié)一下JVM中的內(nèi)存劃分以及各個(gè)區(qū)域的作用。

在JVM規(guī)范中定義了5種運(yùn)行時(shí)的數(shù)據(jù)區(qū)域:程序計(jì)數(shù)器(Program Counter Register)、Java虛擬機(jī)棧(JVM Stacks)、堆(Heap)、方法區(qū)(Method Area)、運(yùn)行時(shí)常量池(Runtime Constant Pool)、本地方法棧(Native Method Stack)。在周志明的書中還提到了直接內(nèi)存(Direct Memory),它并不是JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)域的一部分,在JVM的規(guī)范中也沒有相關(guān)的定義。下面分別來說明各自的用途。

程序計(jì)數(shù)器

程序計(jì)數(shù)器,也叫PC Register。它的用途很單一,但是卻是很多功能的基礎(chǔ)。如果線程當(dāng)前執(zhí)行的是Native方法,那么寄存器里的值就是Undefined;如果線程當(dāng)前執(zhí)行的是非Native方法,那么寄存器里的值就是當(dāng)前執(zhí)行的JVM字節(jié)碼指令的地址。像我們常用的分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等都依賴于它。

由于JVM支持多個(gè)線程同時(shí)執(zhí)行,所以每個(gè)線程都有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各個(gè)線程互不影響,這類內(nèi)存區(qū)域也稱之為線程私有的。

Java虛擬機(jī)棧

虛擬機(jī)棧也是線程私有的,隨著一個(gè)線程的創(chuàng)建而創(chuàng)建,主要用來存儲(chǔ)棧幀(Stack Frame)。什么是棧幀呢?在Java中,每個(gè)方法在執(zhí)行時(shí)就會(huì)先創(chuàng)建一個(gè)棧幀并放入虛擬機(jī)棧中,在方法執(zhí)行完畢時(shí)再?gòu)奶摂M機(jī)棧中移除該棧幀。它主要用來存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。我們常說的堆(Heap)和棧(Stack)中的棧,指的就是虛擬機(jī)棧。

在JVM規(guī)范中并沒有對(duì)虛擬機(jī)??臻g的大小做限制,可以設(shè)置為固定大小的,也可以設(shè)置為可擴(kuò)展的。但是在規(guī)范中定義了兩種異常情況:

  • 如果計(jì)算時(shí)請(qǐng)求的??臻g大于虛擬機(jī)棧的最大值,則會(huì)拋出StackOverflowError異常;
  • 如果虛擬機(jī)棧設(shè)置為可擴(kuò)展的并且無法再獲取更多內(nèi)存時(shí),則會(huì)拋出OutOfMemoryError異常。

相比而言,堆在JVM管理的內(nèi)存區(qū)域中屬于最大的一塊,隨著虛擬機(jī)的啟動(dòng)而創(chuàng)建,用來存儲(chǔ)所有的class實(shí)例和數(shù)組,所有線程共享這一區(qū)域,該區(qū)域也是垃圾回收的主要區(qū)域。雖然JVM規(guī)范中說所有的對(duì)象實(shí)例都在該區(qū)域分配空間,但是隨著JIT技術(shù)的逐步發(fā)展,這一說法也不嚴(yán)謹(jǐn)了。

堆空間的大小也可以設(shè)置為固定大小,或者可擴(kuò)展的。但不管是何種方式,規(guī)范中還是定義了一種異常場(chǎng)景:

  • 如果計(jì)算需要更多的堆空間而無法滿足時(shí),則會(huì)拋出OutOfMemoryError異常。

方法區(qū)

方法區(qū)和堆一樣,也是隨著虛擬機(jī)啟動(dòng)而創(chuàng)建,所有線程共享,主要用來存儲(chǔ)被JVM加載的類信息、常量、靜態(tài)變量等信息。

JVM規(guī)范中并未嚴(yán)格要求要對(duì)該區(qū)域進(jìn)行垃圾回收,但是HotSpot虛擬機(jī)在垃圾回收的時(shí)候還是會(huì)考慮該區(qū)域,在分代垃圾回收中所說的“永久代”指的就是方法區(qū)。方法區(qū)的大小也可以設(shè)置為固定大小,或者可擴(kuò)展的。但不管是何種方式,規(guī)范中還是定義了一種異常場(chǎng)景:

  • 如果計(jì)算需要更多的方法區(qū)空間而無法滿足時(shí),則會(huì)拋出OutOfMemoryError異常。

運(yùn)行時(shí)常量池

運(yùn)行時(shí)常量池是方法區(qū)的一部分,用于存儲(chǔ)編譯期生成的各種字面量和符號(hào)引用。在Java中并不要求常量一定只有編譯期才能產(chǎn)生,運(yùn)行期間也可能將新的常量放入池中,例如String類的intern()方法。

每個(gè)運(yùn)行時(shí)常量池都是隨著一個(gè)類或者接口的創(chuàng)建而創(chuàng)建的。在規(guī)范中定義了一種異常場(chǎng)景:

  • 在創(chuàng)建一個(gè)類或者接口時(shí),如果運(yùn)行時(shí)的常量池?zé)o法分配到足夠的空間時(shí),則會(huì)拋出OutOfMemoryError異常。

本地方法棧

本地方法棧和虛擬機(jī)棧類似,也是線程私有的,隨著一個(gè)線程的創(chuàng)建而創(chuàng)建,只不過虛擬機(jī)棧是用來服務(wù)Java方法調(diào)用,而本地方法棧是用來服務(wù)本地方法調(diào)用的。

在JVM規(guī)范中并沒有對(duì)本地方法棧空間的大小做限制,可以設(shè)置為固定大小的,也可以設(shè)置為可擴(kuò)展的。在規(guī)范中也定義了兩種異常情況:

  • 如果計(jì)算時(shí)請(qǐng)求的棧空間大于本地方法棧的最大值,則會(huì)拋出StackOverflowError異常;
  • 如果本地方法棧設(shè)置為可擴(kuò)展的并且無法再獲取更多內(nèi)存時(shí),則會(huì)拋出OutOfMemoryError異常。

直接內(nèi)存*

直接內(nèi)存不受虛擬機(jī)參數(shù)的控制,在NIO中有一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方式,它可以通過Native方法在堆外分配內(nèi)存,然后通過DirectByteBuffer對(duì)象來引用這塊內(nèi)存。因?yàn)楸苊饬嗽贘ava堆和Native堆之間來回復(fù)制數(shù)據(jù),從而在某些場(chǎng)景中能夠得到性能的提升。一旦使用的直接內(nèi)存超過了物理內(nèi)存的總和,則會(huì)拋出OutOfMemoryError異常。

參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容