前一篇我們從整體上認(rèn)識一下JVM由哪些部分組成,現(xiàn)在我們開始著重了解JVM的核心部分-運(yùn)行時數(shù)據(jù)區(qū)
Java虛擬機(jī)在運(yùn)行程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū),基于內(nèi)存是否能被線程所共享蜕猫,內(nèi)存可分為上圖藍(lán)色和白色兩大塊區(qū)域胶惰,藍(lán)色區(qū)域表示所有線程都會向此區(qū)域讀寫數(shù)據(jù)垫桂,白色區(qū)域表示這些區(qū)域是線程私有的,每條線程都有自己的虛擬機(jī)棧专控、本地方法棧抹凳、程序計(jì)數(shù)器,各條線程之間的棧和計(jì)數(shù)器相互隔離伦腐。
程序計(jì)數(shù)器(Program Counter Register)
可看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器却桶。由于JVM的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實(shí)現(xiàn)的,在任何一個確定的時刻蔗牡,一個處理器(對于多核處理器來說就是一個內(nèi)核)都會執(zhí)行一條線程中的指令,因此嗅剖,為了線程切換后能恢復(fù)到正確的執(zhí)行位置辩越,每條線程都需要一個獨(dú)立的程序計(jì)數(shù)器,各條線程之間互不影響信粮,獨(dú)立存儲黔攒。
- 如果線程正在執(zhí)行的是一個Java方法,則計(jì)數(shù)器內(nèi)存儲的是虛擬機(jī)字節(jié)碼指令的地址
- 如果線程正在執(zhí)行的是一個Native方法强缘,則計(jì)數(shù)器的值為空(Undefined)
此內(nèi)存區(qū)域是唯一一個在JVM規(guī)范中沒有明確規(guī)定任何OutOfMemoryError情況的區(qū)域督惰。
Java虛擬機(jī)棧(JVM Stack)
它描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的時候都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧旅掂、動態(tài)鏈接赏胚、方法出入口等信息。每一個方法從調(diào)用直至執(zhí)行完成的過程商虐,就對應(yīng)著一個棧幀在虛擬機(jī)棧中的入棧和出棧的過程觉阅。它的生命周期與線程相同。
這個區(qū)域規(guī)定了兩種異常情況:
- 如果線程所請求的深度超過虛擬機(jī)所允許的深度秘车,將拋出StackOverflowError典勇,常見于遞歸調(diào)用
- 如果虛擬機(jī)動態(tài)擴(kuò)展時無法申請到足夠的內(nèi)存,將會拋出OutOfMemoryError叮趴,比如割笙,程序中不斷創(chuàng)建新的線程,每新建一條線程都會劃分出一塊線程棧眯亦,當(dāng)程序占用的內(nèi)存到達(dá)系統(tǒng)所允許的上限時( Linux上可以通過
ulimit -v <n_bytes>
)伤溉,就會拋出此異常般码。
本地方法棧(Native Method Stack)
與Java虛擬機(jī)棧的作用非常相似,區(qū)別是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)谈火,而本地方法棧則是為虛擬機(jī)使用到的Native方法服務(wù)侈询。此區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常。
HotSpot虛擬機(jī)中糯耍,直接把Java虛擬機(jī)棧和本地方法棧合二為一扔字。
Java堆(Heap)
JVM所管理的內(nèi)存中最大的一塊,是垃圾回收器管理的主要區(qū)域(因此也稱為“GC堆”温技,Garbage Collected Heap)革为,所有線程共享,在虛擬機(jī)啟動的時候創(chuàng)建舵鳞。幾乎所有的對象實(shí)例以及數(shù)組都要在堆上分配震檩,這里沒有說那么絕對,是因?yàn)橥ㄟ^“逃逸分析”這種技術(shù)蜓堕,JIT編譯器在編譯階段就可以進(jìn)行一些高效的優(yōu)化:棧上分配(Stack Allocation)抛虏、標(biāo)量替換(Scalar Replacement),從而使得對象(必要時進(jìn)行一些分解)被分配到棧內(nèi)存套才,這樣對象不僅訪問速度快迂猴,所占用的內(nèi)存也可以隨棧幀出棧而自動銷毀,減輕了垃圾回收器的壓力背伴。
當(dāng)堆中沒有足夠空間可以用來分配實(shí)例對象沸毁,并且堆也無法再擴(kuò)展時,將會拋出OutOfMemoryError異常傻寂。
由于本章節(jié)主要從整體上了解運(yùn)行時數(shù)據(jù)區(qū)息尺,堆內(nèi)存的很多細(xì)節(jié)將在下一個章節(jié)了解。
方法區(qū)(Method Area)
所有線程共享疾掰,用于存儲JVM加載的類信息搂誉、常量、靜態(tài)變量静檬、JIT編譯器編譯后的代碼等數(shù)據(jù)勒葱。它只是JVM規(guī)范中定義的一個概念,是堆的一個邏輯部分巴柿,為了與普通堆區(qū)分開來凛虽,有一個別名叫做Non-Heap(非堆)。
永久代(Permanent Generation)
在Java 8之前的HotSpot虛擬機(jī)中广恢,有一個被稱為“永久代”的區(qū)域凯旋,它與堆內(nèi)存連續(xù),本質(zhì)上與方法區(qū)并不等價,它其實(shí)就是方法區(qū)的一個實(shí)現(xiàn)至非,是HotSpot特有的钠署,HotSpot的設(shè)計(jì)團(tuán)隊(duì)選擇把GC分代收集擴(kuò)展至方法區(qū),或者說使用永久代來實(shí)現(xiàn)方法區(qū)荒椭,這樣垃圾回收器就可以像管理Java堆一樣管理這部分內(nèi)存谐鼎,省去了專門為方法區(qū)編寫內(nèi)存管理的代碼元空間(Metaspace)
從Java 8開始,永久代被移除趣惠,取而代之的是元空間狸棍,它直接從操作系統(tǒng)分配內(nèi)存,獨(dú)立且可以自由擴(kuò)展味悄,最大可分配空間就是系統(tǒng)可用空間草戈,因此不會遇到PermGen的內(nèi)存溢出錯誤。一旦發(fā)生內(nèi)存泄露侍瑟,會占用大量本地內(nèi)存唐片。元空間以ClassLoader為單位獨(dú)立分配本地內(nèi)存,不受堆GC管理涨颜,需要由元空間虛擬機(jī)(Metaspace VM)負(fù)責(zé)來管理费韭。
本章節(jié)大致了解了一下運(yùn)行時數(shù)據(jù)區(qū),其中最重要的堆內(nèi)存將在下一個章節(jié)介紹庭瑰。