1 java內(nèi)存結(jié)構(gòu)圖
理解學(xué)習(xí)java虛擬機(jī)內(nèi)存結(jié)構(gòu),能夠幫助我們更加清楚知道java程序在操作的時(shí)候如果存儲(chǔ)數(shù)據(jù),是java虛擬機(jī)的性能優(yōu)化(GC調(diào)優(yōu))的基礎(chǔ)。
談到JVM內(nèi)存結(jié)構(gòu),當(dāng)然少不了這個(gè)經(jīng)典的結(jié)構(gòu)圖

上圖清晰描述了JVM的內(nèi)存結(jié)構(gòu),分別有以下幾個(gè)部分組成
- 堆(Heap):線程共享。所有的對象實(shí)例以及數(shù)組都要在堆上分配?;厥掌髦饕芾淼膶ο?。
- 方法區(qū)(Method Area):線程共享。存儲(chǔ)類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼。
- 方法棧(JVM Stack):線程私有。存儲(chǔ)局部變量表、操作棧、動(dòng)態(tài)鏈接、方法出口,對象指針。
- 本地方法棧(Native Method Stack):線程私有。為虛擬機(jī)使用到的Native 方法服務(wù)。如Java使用c或者c++編寫的接口服務(wù)時(shí),代碼在此區(qū)運(yùn)行。
- 程序計(jì)數(shù)器(Program Counter Register):線程私有。有些文章也翻譯成PC寄存器(PC Register),同一個(gè)東西。它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。指向下一條要執(zhí)行的指令。
2 java內(nèi)存模塊分析
以下這個(gè)圖,幫忙理解jvm的一些參數(shù)控制

2.1 堆
堆的作用是存放對象實(shí)例和數(shù)組。從結(jié)構(gòu)上來分,可以分為新生代和老年代。而新生代又可以分為Eden 空間、From Survivor 空間(s0)、To Survivor 空間(s1)。 所有新生成的對象首先都是放在新生代的。需要注意,Survivor的兩個(gè)區(qū)是對稱的,沒先后關(guān)系,所以同一個(gè)區(qū)中可能同時(shí)存在從Eden復(fù)制過來的對象,和從前一個(gè)Survivor復(fù)制過來的對象。而且,Survivor區(qū)總有一個(gè)是空的。
新生代: 新生代由 Eden 與 Survivor Space(S0,S1)構(gòu)成,大小通過-Xmn參數(shù)指定,Eden 與 Survivor Space 的內(nèi)存大小比例默認(rèn)為8:1,可以通過-XX:SurvivorRatio 參數(shù)指定,比如新生代為10M 時(shí),Eden分配8M,S0和S1各分配1M。
Eden: 希臘語,意思為伊甸園,在圣經(jīng)中,伊甸園含有樂園的意思,根據(jù)《舊約·創(chuàng)世紀(jì)》記載,上帝耶和華照自己的形像造了第一個(gè)男人亞當(dāng),再用亞當(dāng)?shù)囊粋€(gè)肋骨創(chuàng)造了一個(gè)女人夏娃,并安置他們住在了伊甸園。
大多數(shù)情況下,對象在Eden中分配,當(dāng)Eden沒有足夠空間時(shí),會(huì)觸發(fā)一次Minor GC,虛擬機(jī)提供了-XX:+PrintGCDetails/-Xlog:gc*參數(shù),告訴虛擬機(jī)在發(fā)生垃圾回收時(shí)打印內(nèi)存回收日志。Survivor: 意思為幸存者,是新生代和老年代的緩沖區(qū)域。當(dāng)新生代發(fā)生GC(Minor GC)時(shí),會(huì)將存活的對象移動(dòng)到S0內(nèi)存區(qū)域,并清空Eden區(qū)域,當(dāng)再次發(fā)生Minor GC時(shí),將Eden和S0中存活的對象移動(dòng)到S1內(nèi)存區(qū)域。
存活對象會(huì)反復(fù)在S0和S1之間移動(dòng),當(dāng)對象從Eden移動(dòng)到Survivor或者在Survivor之間移動(dòng)時(shí),對象的GC年齡自動(dòng)累加,當(dāng)GC年齡超過默認(rèn)閾值15時(shí),會(huì)將該對象移動(dòng)到老年代,可以通過參數(shù)-XX:MaxTenuringThreshold 對GC年齡的閾值進(jìn)行設(shè)置。老年代: 老年代的空間大小即-Xmx 與-Xmn 兩個(gè)參數(shù)之差,用于存放經(jīng)過幾次Minor GC之后依舊存活的對象。當(dāng)老年代的空間不足時(shí),會(huì)觸發(fā)Major GC/Full GC,速度一般比Minor GC慢10倍以上。
控制參數(shù)
- -Xms設(shè)置堆的初始空間大小。
- -Xmx設(shè)置堆的最大空間大小。
- -Xmn控制新生代大小。(jdk1.4之后使用)
- -XX:NewSize設(shè)置新生代最小空間大小
- -XX:MaxNewSize設(shè)置新生代最小空間大小,
- -XX:NewRatio 老年代:年輕代的比例, 如2 代表 young:old = 1:2
- -XX:SurvivorRatio Eden:Survivor的比例 如8 代表 eden:s0:s1 = 8:1:1
垃圾回收
此區(qū)域是垃圾回收的主要操作區(qū)域。
異常情況
如果在堆中沒有內(nèi)存完成實(shí)例分配,并且堆也無法再擴(kuò)展時(shí),將會(huì)拋出OutOfMemoryError 異常
2.2 方法區(qū)
方法區(qū)(Method Area)與Java 堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。雖然Java 虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻有一個(gè)別名叫做Non-Heap(非堆),目的應(yīng)該是與Java 堆區(qū)分開來。
很多人愿意把方法區(qū)稱為“永久代”(Permanent Generation),本質(zhì)上兩者并不等價(jià),僅僅是因?yàn)镠otSpot虛擬機(jī)的設(shè)計(jì)團(tuán)隊(duì)選擇把GC 分代收集擴(kuò)展至方法區(qū),或者說使用永久代來實(shí)現(xiàn)方法區(qū)而已。對于其他虛擬機(jī)(如BEA JRockit、IBM J9 等)來說是不存在永久代的概念的。在Java8中永生代徹底消失了。
控制參數(shù)
-XX:PermSize 設(shè)置最小空間
-XX:MaxPermSize 設(shè)置最大空間。
-XX:MetaspaceSize=8m (1.8之后去掉方法區(qū),使用元數(shù)據(jù)區(qū))
-XX:MaxMetaspaceSize=80m(1.8之后去掉方法區(qū),使用元數(shù)據(jù)區(qū))
-XX:CompressedClassSpaceSize(1.8之后去掉方法區(qū),使用元數(shù)據(jù)區(qū))
在JDK8后,方法區(qū)被移除了,引入了新的空間Metaspace來存放類的信息。
Metaspace不在虛擬機(jī)中,而是使用本地內(nèi)存,這樣困擾我們的PermenGen的內(nèi)存就不存在了,其上限變成了物理內(nèi)存,當(dāng)然可以通過參數(shù)進(jìn)行設(shè)置。
-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m
那么曾經(jīng)在其中的常量池被轉(zhuǎn)移到哪里了?答案是在堆中。
關(guān)于PermGen 和 metaspace, 請一定看看,請參考 深入理解堆外內(nèi)存 Metaspace,
聊聊jvm的PermGen與Metaspace
垃圾回收
對此區(qū)域會(huì)涉及但是很少進(jìn)行垃圾回收。這個(gè)區(qū)域的內(nèi)存回收目標(biāo)主要是針對常量池的回收和對類型的卸載,一般來說這個(gè)區(qū)域的回收“成績”比較難以令人滿意。
異常情況
根據(jù)Java 虛擬機(jī)規(guī)范的規(guī)定, 當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí),將拋出OutOfMemoryError。
2.3 方法棧
每個(gè)線程會(huì)有一個(gè)私有的棧。每個(gè)線程中方法的調(diào)用又會(huì)在本棧中創(chuàng)建一個(gè)棧幀。在方法棧中會(huì)存放編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference 類型,它不等同于對象本身。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運(yùn)行期間不會(huì)改變局部變量表的大小。
控制參數(shù)
-Xss控制每個(gè)線程棧的大小。
異常情況
在Java 虛擬機(jī)規(guī)范中,對這個(gè)區(qū)域規(guī)定了兩種異常狀況:
- StackOverflowError: 異常線程請求的棧深度大于虛擬機(jī)所允許的深度時(shí)拋出;
- OutOfMemoryError 異常: 虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展,當(dāng)擴(kuò)展時(shí)無法申請到足夠的內(nèi)存時(shí)會(huì)拋出。
2.4 本地方法棧
本地方法棧(Native Method Stacks)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,其區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java 方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機(jī)使用到的Native 方法服務(wù)。
控制參數(shù)
在Sun JDK中本地方法棧和方法棧是同一個(gè),因此也可以用-Xss控制每個(gè)線程的大小。
異常情況
與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會(huì)拋出StackOverflowError 和OutOfMemoryError異常。
2.5 程序計(jì)數(shù)器
它的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。
異常情況
此內(nèi)存區(qū)域是唯一一個(gè)在Java 虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError 情況的區(qū)域。
參考鏈接