JVM之內(nèi)存分配和回收機制
前言
本篇主要介紹JVM內(nèi)存分配和回收策略,內(nèi)容主要節(jié)選自《深入理解java虛擬機》。
一、內(nèi)存分配策略
1. 堆內(nèi)存模型
組成:
- 新生代 默認占堆空間的三分之一,由于在新生代對象大多都是朝生夕死,則采用的是復(fù)制算法,在復(fù)制的期間會有頻繁的Minor GC。
- 老年代 默認占堆空間的三分之二,老年代對象大多都是長期存活的,則采用的是標(biāo)記算法,老年代的Major GC開銷非常大。
- Eden 新生代分為2個區(qū) Eden和Survivor區(qū) 其中Eden區(qū)默認占新生代十分之八的空間,對象優(yōu)先進入Eden區(qū)。
- Survivor 當(dāng)Eden區(qū)內(nèi)存滿了之后會進行一次Minor GC存活對象會進入Survivor區(qū),默認占新生代十分之二空間, 其中它又均分為From區(qū)和To區(qū),在Survivor區(qū)的對象每熬過一次從From區(qū)到TO區(qū)則年齡+1。
大多情況下,對象在新生代Eden區(qū)中分配。當(dāng)Eden沒有足夠的空間分配對象時虛擬機會發(fā)起一次Minor GC。
2.2 大對象直接到老年代
大對象即需要大量連續(xù)內(nèi)存空間的對象(例如很長的字符串及數(shù)組)。虛擬機提供了一個-XX:PretenureSizeThreshoId參數(shù),令大于這個設(shè)置值的對象直接在老年代分配,這樣做的目的是避免在Eden區(qū)及兩個區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。注意PretenureSizeThreshoId參數(shù)只對Serial和ParNew兩款收集器有效。
2.3 動態(tài)年齡判斷
為了能更好地適應(yīng)不同程序的內(nèi)存狀況,虛擬機并不是永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進人老年代,無須等到MaxTenuringThreshoId中要求的年齡。
2.4 內(nèi)存擔(dān)保機制
在發(fā)生Minor GC之前,虛擬機會先檢查Survivor空間是否夠用,如果夠用則直接進行Minor GC。否則進行檢查老年代最大連續(xù)可用空間是否大于新生代的總和,假如大于,那么這個時候發(fā)生Minor GC是安全的。假如不大于,那么需要判斷HandlePromotionFailure設(shè)置是否允許擔(dān)保失敗。假如允許,則繼續(xù)判定老年代最大可用的連續(xù)空間是否大于平均晉升到老年代對象的平均值,如果大于,這個時候可以發(fā)生Minor GC ,如果小于或者設(shè)置HandlePromotionFailure不允許擔(dān)保失敗,則需要做一次Full GC。通常會把HandlePromotionFailure開關(guān)打開,以減少Full GC。
2.5 長期存活對象
虛擬機給每個對象定義了一個對象年齡(Age)計數(shù)器(存在于對象頭中)。如果對象在Eden出生并經(jīng)過第一次MinorGC后仍然存活,并且能被Survivor容納的話,將被移動到Survwor空間中,并且對象年齡設(shè)為1。對象在Survivor區(qū)中每“熬過”一次MinorGC,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認為15歲),就將會被晉升到老年代中。對象晉升老年代的年齡閾值,可以通過參數(shù)-XX:MaxTenuringThreshoId設(shè)置。
二、對象存活
判斷對象存活一般有兩種方式: 引用計數(shù)算法和可達性分析算法。
1.引用計數(shù)算法
原理:
- 引用計數(shù)算法(Reference Counting)比較簡單,對每個對象保存一個整型的引用計數(shù)器屬性。用于記錄對象被引用的情況。
- 對于一個對象A,只要有任何一個對象引用了A,則A的引用計數(shù)器就加1;當(dāng)引用失效時,引用計數(shù)器就減1。只要對象A的引用計數(shù)器的值為0,即表示對象A不可能再被使用,可進行回收。
優(yōu)點:
- 實現(xiàn)簡單,垃圾對象便于辨識;
- 判定效率高,回收沒有延遲性。
缺點:
- 它需要單獨的字段存儲計數(shù)器,這樣的做法增加了存儲空間的開銷。
- 每次復(fù)制都需要更新計數(shù)器,伴隨著加法和減法操作,這增加了時間開銷。
- 引用計數(shù)器有一個嚴重的問題,即無法處理循環(huán)引用的情況。這是一條致命缺陷,導(dǎo)致在Java的垃圾回收器中沒有使用這類算法。
2.可達性分析算法
定義:相對于引用計數(shù)算法而言,可達性分析算法不僅同樣具備實現(xiàn)簡單和執(zhí)行高效等特點,更重要的是該算法可以有效地解決在引用計數(shù)算法中循環(huán)引用的問題,防止內(nèi)存泄漏的發(fā)生。
原理:
- 可達性分析算法是以根對象集合(GCRoots)為起始點,按照從上至下的方式搜索被根對象集合所連接的目標(biāo)對象是否可達。
- 使用可達性分析算法后,內(nèi)存中的存活對象都會被根對象集合直接或間接連接著,搜索所走過的路徑稱為引用鏈(Reference Chain)。
- 如果目標(biāo)對象沒有任何引用鏈相連,則是不可達的,就意味著該對象己經(jīng)死亡,可以標(biāo)記為垃圾對象。
- 在可達性分析算法中,只有能夠被根對象集合直接或者間接連接的對象才是存活對象。
GC ROOT 對象:
- 虛擬機棧中引用的對象;
比如:各個線程被調(diào)用的方法中使用到的參數(shù)、局部變量等。 - 本地方法棧內(nèi) JNI(通常說的本地方法)引用的對象;
- 方法區(qū)中類靜態(tài)屬性引用的對象;
比如:Java類的引用類型靜態(tài)變量 - 方法區(qū)中常量引用的對象;
比如:字符串常量池(string Table)里的引用 - 所有被同步鎖 synchronized 持有的對象;
- Java虛擬機內(nèi)部的引用。
基本數(shù)據(jù)類型對應(yīng)的 Class 對象,一些常駐的異常對象(如:NullPointerException、OutOfMemoryError),系統(tǒng)類加載器。
3.再談引用
- 強引用:強引用在代碼中普遍存在,例如Object obj=new Object() 這類的引用。只要強引用存在,垃圾回收器永遠不會回收掉被引用的對象。
- 軟引用:軟引用來描述一些還有用但并非必須的對象,在系統(tǒng)要發(fā)生內(nèi)存溢出之前,將會把這些對象列入回收范圍之中進行第二次回收,如果第二次回收還沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常。
- 弱引用:弱引用也是用來描述必須對象的,但是它的強度比軟引用更弱,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾回收發(fā)生之前。當(dāng)垃圾回收器工作時,無論內(nèi)存是否足夠,都回收掉只被弱引用關(guān)聯(lián)的對象。
- 虛引用:它是最弱的一種引用關(guān)系。它無法通過虛引用來取得一個對象實例。唯一目的就是能在這個對象被回收之前會收到一個系統(tǒng)通知。
三、內(nèi)存回收
1.堆內(nèi)存回收
是JVM所管理內(nèi)存最大的一塊,也是gc回收的主要區(qū)域。
1.1 哪些對象能回收?
堆內(nèi)存中對象存活是使用可達性分析算法來判斷,其中非存活對象由GC回收掉。這個就是虛擬機需要回收堆的對象。
1.2 如何回收?
- Minor GC:新生代收集,目標(biāo)只是新生代的垃圾收集;
- Major GC:老年代收集,目標(biāo)是老年代的垃圾收集(具體說只有CMS會有單獨收集老年代的行為);
- Full GC:收集整個java堆和方法區(qū)的垃圾收集。這里補充說明一下雖然網(wǎng)上很多說什么Full GC就是Major GC,在這里我要重申一下并不是,具體看書上描述如下:
Mixed GC:收集整個新生代以及部分老年代的垃圾收集,僅G1支持。(類似于Full GC)
1.3 什么時候回收?
Minor GC觸發(fā)條件:
- Eden區(qū)域滿了,會觸發(fā)Minor GC;
- 新生對象需要分配到新生代的Eden,當(dāng)Eden區(qū)的內(nèi)存不夠時需要進行MinorGC。
Major GC觸發(fā)條件:
- 老年代區(qū)域設(shè)置的閾值空間滿了,會觸發(fā)Major GC;
- 新對象需要分配到老年代,此時老年代設(shè)置閾值可用空間不足時觸發(fā)Major GC。
Full GC觸發(fā)條件:
- 內(nèi)存擔(dān)保機制 ,Survivor空間不足時,判斷是否允許擔(dān)保失敗,如果不允許則進行Full GC。如果允許,并且每次晉升到老年代的對象平均大小>老年代最大可用連續(xù)內(nèi)存空間,也會進行Full GC;
- MinorGC后存活的對象超過了老年代剩余空間;
- 方法區(qū)內(nèi)存不足時;
- 程序中調(diào)用了System.gc()方法,可用通過-XX:+ DisableExplicitGC來禁止調(diào)用System.gc;
- CMS GC異常,CMS運行期間預(yù)留的內(nèi)存無法滿足程序需要,就會出現(xiàn)一次“Concurrent Mode Failure”失敗,會觸發(fā)Full GC。
2.方法區(qū)回收
方法區(qū)主要回收廢棄的常量池和不再使用類型,但這個2類對象存活的判斷還不一樣。
2.1 常量池
同堆的對象存活類似-可達性分析法,具體請參考之前的可達性分析法。
2.2 類型數(shù)據(jù)
- 該類索引的實例都已經(jīng)被回收;
- 加載該類的類加載器已經(jīng)被回收;
- 該類對應(yīng)的java.lang.class對象沒有任何地方被引用。
以上都是我簡單總結(jié),以下是書上關(guān)于方法區(qū)回收描述的內(nèi)容:
其實從書上就可以看出來,關(guān)于方法區(qū)OOM問題大都是在程序中是有大量使用反射、動態(tài)代理、CGLIB等框架,如果在實際開發(fā)中遇到關(guān)于可以從以上幾個維度來定位問題。
總結(jié)
本篇所有理論知識都是摘抄于《深入理解java虛擬機》,有部分是自己簡單總結(jié),JVM內(nèi)存分配和回收是我們在分析JVM調(diào)優(yōu)和相關(guān)問題的基石,建議看完我本篇的去多看幾遍《深入理解java虛擬機》。
相關(guān)文章
Java 獲取兩個List的交集和差集,以及應(yīng)用場景操作
這篇文章主要介紹了Java 獲取兩個List的交集和差集,以及應(yīng)用場景操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09淺談Storm在zookeeper上的目錄結(jié)構(gòu)
這篇文章主要介紹了淺談Storm在zookeeper上的目錄結(jié)構(gòu)的相關(guān)內(nèi)容,涉及storm使用zookeeper的操作以及詳細結(jié)構(gòu)圖,具有一定參考價值,需要的朋友可以了解下。2017-10-10