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