【JVM】堆體系結構及其內存調優

堆體系結構

一個JVM實例只存在一個堆內存,堆內存的大小是可調節的。類加載器讀取類文件後,需要把類、方法、常量、變量放在堆內存中,保存所有引用類型的真實信息,以方便執行器指向,堆內存分爲三個部分:年輕代、老年代、永久代。

Java7之前,堆內存在邏輯上分爲:年輕代、老年代、永久代。物理上分爲:年輕代、老年代

 Java8:永久代 ---> 元空間

 

新生區是類的誕生、成長、消亡的區域。一個類在新生區產生,最後被垃圾回收器收集。新生區分爲伊甸區和倖存者區。倖存者區分爲倖存0區,倖存1區。

當伊甸區空間用完的時候,程序還需要創建對象,JVM的垃圾回收器將對伊甸區進行垃圾回收(Minor GC),將伊甸區中不再被其他對象引用的對象進行銷燬,將剩餘的對象移動到倖存0區。

若倖存0區(from區)滿了,對倖存0區進行垃圾回收,將剩餘的對象移動到倖存1區。如果倖存1區(to區)滿了,再移動到養老區。

如果養老區滿了,就產生了Major GC(Full GC),進行養老區的內存清理。如果執行了Full GC後依然無法進行對象的保存,就會產生OOM異常,OutOfMemoryError。

異常:java.lang.OutOfMemoryError: Java heap space

JVM堆內存不夠,原因:

  • JVM的堆內存設置的太小,可以調整-Xms、-Xmx
  • 代碼中創建了大量的大對象,並且長時間不能被垃圾回收器收集(存在被引用)

Minor GC的過程

Java堆從GC的角度可以細分爲新生代(Eden區、from 存活區、to 存活區,空間比例8:1:1)和老年代(空間比例1:2)。

複製 ☞ 清空 ☞ 互換

1. eden、survivor from 複製到 survivor to,對象年齡+1。

當eden區滿,觸發第一次GC,存活對象拷貝到survivor from區。當eden區再次觸發GC,會掃描eden和from,對這兩個區進行垃圾回收,將存活的對象,複製到to區,對象年齡+1。(如果有對象年齡達到了老年的標準,拷貝到老年代,對象年齡+1)

2. 清空eden、survivor from

清空eden和survivor from中對象,此時from爲。

3. survivor from 和 survivor to 互換

to區存在對象,變成下一次GC的from區,from區成爲下一次GC的to區,部分對象會在form和to區域複製往來15次(JVM的MaxTenuringshold參數默認是15),如果最終還是存活,就存入老年代。

方法區和永久代

參考自博客:https://www.jianshu.com/p/66e4e64ff278

在JDK1.6及之前,運行時常量池是方法區的一個部分,同時方法區裏面存儲了類的元數據信息、靜態變量、即時編譯器編譯後的代碼(比如spring 使用IOC或者AOP創建bean時,或者使用cglib,反射的形式動態生成class信息等)等。在JDK1.7及以後,JVM已經將運行時常量池從方法區中移了出來,在JVM堆開闢了一塊區域存放常量池。

方法區和堆都是各個線程共享的內存區域,方法區用於存儲虛擬機加載的類信息、普通常量、靜態常量、編譯器編譯後的代碼等,雖然JVM規範將方法區描述爲堆的一個邏輯部分,但它還有一個別名叫Non-Heap,目的是和堆分開。

方法區常被成爲永久代,嚴格來說二者不同,只是用永久代來實現方法區而已,方法區和永久代的關係很像Java中接口和類的關係,類實現了接口,而永久代就是HotSpot虛擬機對虛擬機規範中方法區的一種實現方式。

永久代在JDK1.7之前有,是一個常駐內存區域,用於存放JDK自身攜帶的class、interface的元數據,也就是說它存儲的是運行環境必須的類信息,被裝在進此區域的數據是不會被垃圾回收器回收掉的,關閉jvm纔會釋放這個區域所佔的內存。

HotSpot虛擬機中存在三種垃圾回收現象,minor GC、major GC和full GC。對新生代進行垃圾回收叫做minor GC,對老年代進行垃圾回收叫做major GC,同時對新生代、老年代和永久代進行垃圾回收叫做full GC。許多major GC是由minor GC觸發的,所以很難將這兩種垃圾回收區分開。major GC和full GC通常是等價的,收集整個GC堆。但因爲HotSpot VM發展了這麼多年,外界對各種名詞的解讀已經完全混亂了,當有人說“major GC”的時候一定要問清楚他想要指的是上面的full GC還是major GC。

元空間

參考自博客:https://www.jianshu.com/p/66e4e64ff278

HotSpot虛擬機在1.8之後已經取消了永久代,改爲元空間,類的元信息被存儲在元空間中。元空間沒有使用堆內存,而是與堆不相連的本地內存區域。所以,理論上系統可以使用的內存有多大,元空間就有多大,所以不會出現永久代存在時的內存溢出問題。
 
這項改造也是有必要的,永久代的調優是很困難的,雖然可以設置永久代的大小,但是很難確定一個合適的大小,因爲其中的影響因素很多,比如類數量的多少、常量數量的多少等。永久代中的元數據的位置也會隨着一次full GC發生移動,比較消耗虛擬機性能。同時,HotSpot虛擬機的每種類型的垃圾回收器都需要特殊處理永久代中的元數據。將元數據從永久代剝離出來,不僅實現了對元空間的無縫管理,還可以簡化Full GC以及對以後的併發隔離類元數據等方面進行優化。

堆內存調優

在JDK1.7中

在JDK1.8中,元空間取代永久代。元空間和永久代的最大的區別是永久代使用的是JVM的堆內存,元空間不在虛擬機中,而是使用本機物理內存。默認清空下,元空間只受本地內存限制,類的元數據放入本地內存,字符串常量池和類型靜態變量放入java堆,類的元數據的加載量不再受MaxPermSize控制,而是由系統實際的可用空間來控制。

-Xms:初始分配大小,默認爲物理內存的1/64

-Xmx:最大分配內存,默認爲物理內存的1/4

-XX:+PrintGCDetails:輸出詳細的GC處理日誌

配置完Xms、Xmx後的輸出結果

java.lang.OutOfMemoryError: Java heap space異常GC處理日誌:

[GC (Allocation Failure) [PSYoungGen: 2045K->488K(2560K)] 2045K->781K(9728K), 0.0014360 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2534K->488K(2560K)] 2827K->1548K(9728K), 0.0008101 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2171K->504K(2560K)] 4318K->3194K(9728K), 0.0006870 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 2207K->0K(2560K)] [ParOldGen: 7037K->2826K(7168K)] 9245K->2826K(9728K), [Metaspace: 3454K->3454K(1056768K)], 0.0051352 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 5000K->5000K(9728K), 0.0003304 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 5000K->5000K(9728K), 0.0002962 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 5000K->3913K(7168K)] 5000K->3913K(9728K), [Metaspace: 3455K->3455K(1056768K)], 0.0028924 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 3913K->3913K(8704K), 0.0005099 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 3913K->3889K(7168K)] 3913K->3889K(8704K), [Metaspace: 3455K->3455K(1056768K)], 0.0072665 secs] [Times: user=0.06 sys=0.02, real=0.01 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
    at java.lang.StringBuilder.append(StringBuilder.java:208)
    at day05JVM01.T2.main(T2.java:15)

YoungGC

[GC (Allocation Failure)  內存分配失敗

[PSYoungGen: 2045K->488K(2560K)] 2045K->781K(9728K), 0.0014360 secs] 

[GC類型:GC前young區的內存佔用->GC後young區的內存佔用(新生代的總內存)] GC前JVM堆內存佔用->GC後JVM堆內存佔用(JVM堆的總內存),GC耗時

[Times: user=0.00 sys=0.00, real=0.00 secs] 

[GC用戶耗時,系統耗時,實際耗時]

 FullGC

[Full GC (Allocation Failure)

[PSYoungGen: 0K->0K(1536K)]

[ParOldGen: 3913K->3889K(7168K)] 3913K->3889K(8704K),

[Metaspace: 3455K->3455K(1056768K)], 0.0072665 secs]

[Times: user=0.06 sys=0.02, real=0.01 secs]

什麼是GC?

GC是分類收集算法,JVM在進行GC的時候並不是每次對三個區域一起回收,大部分時候是回收新生代。頻繁收集Young區,較少收集Old區,基本不動元空間。GC按照回收的區域分成了:普通GC minor GC和全局GC Full GC

Minor GC:只針對新生代區域的GC,發生在新生代的垃圾收集,因爲大多數JAVA對象存活率都不高,所以Minor GC的操作非常頻繁,垃圾回收的速度比較快。

Full GC:指發生在老年代的垃圾收集操作,出現Full GC,經常會伴隨至少一次的Minor GC(但不絕對)。Full GC的速度一般比Minor GC 慢10倍以上。

GC有四大算法:引用計數法、複製算法、標記清除、標記壓縮。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章