Heap(堆區(qū))
Heap 是 OOM 主要發(fā)源地。堆分為兩大塊:新生代
和老年代
。對象產生之初在新生代,步入暮年時進入老年代,但是老年代也接受新生代無法容納的超大對象。
新生代 = 1Eden + 2 Survivor區(qū)
。絕大部分對象在 Eden 區(qū)生成,當Eden區(qū)填滿時,觸發(fā) Young Garbage Collection,即 YGC
。垃圾回收時在Eden區(qū)實現(xiàn)清除策略,沒有被引用的對象直接回收,依然存活送至 Survivor
區(qū)。Survivor
區(qū)分為 S0
和 S1
兩塊空間。每次 YGC 的時候將存活的對象復制到未使用的空間,然后將當前正在使用的空間完全清除,交換兩塊空間使用狀態(tài)。如果 YGC 要移送的對象大于 Survivor 區(qū)容量的上限,則直接移交老年代。每個對象都有一個計數(shù)器,每次 YGC 時 +1,當其打到一定閾值(默認 15)時晉升老年代。
Metaspace
在 JDK8版本中,元空間的前身 Perm區(qū)
(永久代)已經淘汰。
區(qū)別于永久代,元空間在本地內存中分配。在 JDK8中,Perm 里的所有內容中字符串常量移至堆內存,其他內容包括類元信息、字段、靜態(tài)屬性、方法、常量等移至元空間內。
JVM Stack(虛擬機棧)
JVM 中虛擬機棧是描述 java 方法執(zhí)行的內存區(qū)域,他是線程私有
的。棧中的元素用于支持虛擬機進行方法調用,每個方法從開始調用到執(zhí)行完成的過程,就是棧幀從入棧到出棧的過程。在活動線程中,只有位于棧頂?shù)膸攀怯行У模Q為當前棧幀
。正在執(zhí)行的方法稱為當前方法
,棧幀是方法運行的基本結構。所有指令只能針對當前棧幀進行操作。而 StackOverflowError
表示請求棧溢出,導致內存耗盡,通常出現(xiàn)在遞歸方法
中。
局部變量表
局部變量表是存放方法參數(shù)和變量的區(qū)域。相對于類屬性變量的準備階段和初始化階段來說,局部變量沒有準備階段,必須顯示初始化
。如果是非靜態(tài)方法,則在 index[0]
位置上存儲的是方法所屬對象的實例引用,隨后存儲的是參數(shù)和局部變量。
操作棧
操作棧是一個初始狀態(tài)為空的桶式結構棧。JVM 的執(zhí)行引擎是基于棧
的執(zhí)行引擎,這個棧指的就是操作棧
。
動態(tài)連接
每個棧幀中包含一個在常量池中對當前方法的引用,目的是支持方法調用過程的動態(tài)連接。
方法返回地址
方法執(zhí)行時有兩種退出情況:第一,正常退出(正常執(zhí)行到任何方法返回的返回字節(jié)碼指令,如 RETURN\IRETURN\ARETURN);第二,異常退出。不論何種退出都將返回至方法當前被調用的位置。
退出可能有三種方式:
- 返回值壓入上層調用棧幀
- 異常信息拋給能夠處理的棧幀
- PC計數(shù)器指向方法調用后的下一條指令
Native Method Stacks(本地方法棧)
Native Method Stacks(本地方法棧)在 JVM 內存布局中,也是線程對象私有
的。
虛擬機棧主內
,本地方法棧主外
。本地方法棧為 Native方法
服務。本地方法可以通過JNI 來訪問虛擬機運行時的數(shù)據(jù)區(qū),甚至可以調用寄存器,具有 JVM 相同能力與權限。
Program Counter Register(程序計數(shù)器)
在Program Counter Register(程序計數(shù)器)中,程序計數(shù)器用來存放執(zhí)行指令的偏移量
和行號指示器
等,線程執(zhí)行或恢復都要依賴程序計數(shù)器。程序計數(shù)器在各個線程之間互不影響,此區(qū)域也不會發(fā)生內存溢出異常。
最后,從線程共享
角度來看,堆
和元空間
是所有線程共享
的,而虛擬機棧
、本地方法棧
、程序計數(shù)器
是線程內部私有
的。