Java虛擬機在執(zhí)行java程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域.根據(jù) <<java虛擬機規(guī)范>> 中的規(guī)定,將內(nèi)存區(qū)域劃分為
程序計數(shù)器(Program Counter Register)
,虛擬機棧(VM Stack)
,本地方法棧(Native Method Stack)
,方法區(qū)(Method Area)
和堆(Heap)
五大區(qū)域.
程序計數(shù)器(Program Counter Register)
程序計數(shù)器是一塊很小的內(nèi)存區(qū)域,可以當成當前線程所執(zhí)行的字節(jié)碼的行號指示器.java解釋器通過改變計數(shù)器值來選取下一條指令.分治,循環(huán),跳轉(zhuǎn),異常處理,線程恢復(fù)等需要依賴計數(shù)器完成
特點:
- 每一個線程都有一個獨立的程序計數(shù)器,互不影響.(線程私有)
- 線程執(zhí)行Java方法,計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址.
- 線程執(zhí)行Native方法,計數(shù)器則為空.
- 唯一一個沒有定義
OutOfMemoryError
的區(qū)域.
虛擬機棧(VM Stack)
虛擬機棧它的棧元素是一種叫做棧幀(Stack Frame)
的結(jié)構(gòu).每一個棧幀都包括了 局部變量表(Local Variable Table)
,操作數(shù)棧(Operand Stack)
,動態(tài)鏈接(Dynamic Linking)
,方法返回地址(Return Address)
和一些額外信息.
棧幀是用于支持虛擬機進行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),每一個方法從調(diào)用開始至執(zhí)行完成的過程,都對應(yīng)著一個棧幀在虛擬機棧里面從入棧到出棧的過程.
特點:
- Java虛擬機棧是線程私有的,生命周期與線程一致
- 局部變量表所需的內(nèi)存空間在編譯期間確定,并完成分配.
- 在方法運行期間不會改變局部變量表的大小.
- 如果請求的棧深度大于虛擬機允許的深度,拋出
StackOverflowError
. - 虛擬機棧擴展時無法申請足夠的內(nèi)存,拋出
OutOfMemoryError
本地方法棧(Native Method Stack)
功能與虛擬機棧類似,java線程在調(diào)用本地方法時沃暗,該區(qū)用來存儲本地方法的局部變量表备燃,本地方法的操作數(shù)棧等等信息.區(qū)別在于虛擬機棧執(zhí)行的是java方法,
本地方法棧執(zhí)行的是native方法(c/c++方法).
java是高級編程語言剧蹂,當對一些底層的如操作系統(tǒng)或某些硬件交換信息時谭期,我們使用java來編程實現(xiàn)起來不容易入偷,再者使用java來編程效率也很低下.這時候就可以通過 JNI
方式來調(diào)用 native方法來實現(xiàn).
如果,展示了java與native方法交互的過程,java方法中調(diào)用了C語言方法,產(chǎn)生在本地方法棧中產(chǎn)生一個本地棧幀,這個C語言方法調(diào)用了另一個C語言方法,并且把結(jié)果回調(diào)回java方法中.
一個線程可能在整個生命周期中都執(zhí)行Java方法荆姆,操作他的Java棧肛走;或者他可能毫無障礙地在Java棧和本地方法棧之間跳轉(zhuǎn)忱嘹。
特點 :
- 線程私有,生命周期與線程一致
- 調(diào)用的是 c/c++方法(一般用于底層交互,或者性能優(yōu)化)
- 可拋出
StackOverflowError
和OutOfMemoryError
java堆(Heap)
java虛擬機中最大的內(nèi)存區(qū)域,幾乎所有類實例和數(shù)組的內(nèi)存均從此處分配嘱腥。
- 線程共享
- 在 Java 虛擬機啟動時創(chuàng)建的
- GC管理的主要區(qū)域
- 可位于物理內(nèi)存不連續(xù)的空間.
- 可以是固定大小的,也可以是可擴展的.
- 在沒有內(nèi)存空間并且無法擴展時,拋出
OutOfMemoryError
hotspot中的實現(xiàn)
在hotspot虛擬機中,從內(nèi)存回收的角度來看是采用 分代收集策略.將堆劃分為兩個不同的區(qū)域:
新生代(Young Gen)
和老年代(Old Gen)
.
堆的空間大小 = 新生代 + 老年代; 默認情況下,新生代和老年代的比例是 1:2;
新生代又被劃分為Eden
,From Survivor
和To Survivor
三個區(qū)域;大小比例為 8:1:1
由于新生代采用復(fù)制算法
來管理空間,因此,無論什么時候,總是有一塊 Survivor 區(qū)域是空閑著的拘悦。
新生代實際可用的內(nèi)存空間為90%的新生代空間齿兔。
方法區(qū)(Mthod Area)
方法區(qū)中,存儲著已加載的類信息,常量,靜態(tài)變量,即時編譯后的代碼等數(shù)據(jù).
其中類相關(guān)的信息,如類名,訪問修飾符,常量池,字段描述,方法描述等.
方法區(qū)邏輯上屬于堆的一部分,但是為了與堆進行區(qū)分,通常又叫“非堆”分苇。
方法區(qū)的數(shù)據(jù)是線程共享的.
為何叫方法區(qū)? 方法區(qū)中除了包括“已加載的類的基本信息添诉、常量、靜態(tài)變量等”外组砚,還包括編譯器編譯后的代碼吻商,而且這應(yīng)該是方法區(qū)中主要的一部分掏颊,這可能是為何把這部分內(nèi)存成為方法區(qū)的原因.
注釋 : 類的對象和實例對象存放在 java堆中, 類的元數(shù)據(jù)存放在 方法區(qū)中.
不同jdk(hotspot)版本,方法區(qū)數(shù)據(jù)的變化
JDK 1.6以及之前,方法區(qū)的實現(xiàn)為 永久代(Permanent Gen)
的方式,目的是為了垃圾收集器能像管理java堆一樣管理這部分內(nèi)存.垃圾回收目標是針對常量池的回收和對類型的卸載.
JDK 1.7中糟红,存儲在永久代的部分數(shù)據(jù)就已經(jīng)轉(zhuǎn)移到Java Heap或者Native Heap。但永久代仍存在于JDK 1.7中乌叶,并沒有完全移除盆偿,如符號引用(Symbols)轉(zhuǎn)移到了native heap;字面量(interned strings)轉(zhuǎn)移到了Java heap准浴;類的靜態(tài)變量(class statics)存放于定義類型的class對象中,存放在Java heap中.
JDK 1.8中, 完全移除了永久代,取而代之的實現(xiàn)方式成為元空間(Metaspace)
,將類元數(shù)據(jù)放到本地內(nèi)存中事扭,將字符常量池和靜態(tài)變量放到Java堆里。虛擬機會為類的元數(shù)據(jù)明確分配和釋放本地內(nèi)存乐横。
元空間的本質(zhì)和永久代類似求橄,都是對JVM規(guī)范中方法區(qū)的實現(xiàn)。不過元空間與永久代之間的最大區(qū)別在于:元空間并不在虛擬機中葡公,而是使用本地內(nèi)存罐农。
Native memory:本地內(nèi)存,也稱為C-Heap催什,是供JVM自身進程使用的涵亏。當Java Heap空間不足時會觸發(fā)GC,但Native memory空間不夠卻不會觸發(fā)GC蒲凶。即GC不管理元空間(Metaspace)的內(nèi)存.
為什么移除永久代气筋?
- 字符串存在永久代中,容易出現(xiàn)性能問題和內(nèi)存溢出旋圆。
- 永久代大小不容易確定宠默,PermSize指定太小容易造成永久代OOM
- 永久代會為 GC 帶來不必要的復(fù)雜度,并且回收效率偏低灵巧。
- Oracle 可能會將HotSpot 與 JRockit 合二為一搀矫。