1. 程式人生 > >簡述JAVA GC回收機制,深入理解GC原理

簡述JAVA GC回收機制,深入理解GC原理

什麼是“GC”

垃圾回收機制。

為什麼要用“GC”

眾所周知,JAVA 這語言,與C語言不同,Java記憶體(堆記憶體)的分配與回收由JVM垃圾收集器自動完成,比如 C語言自己定義的變數,不用時需要 自己回收這個變數 。JAVA 這就是自動完成了,自動檢測,無用的垃圾,回收,從而釋放記憶體。一個人工,一個自動化。

先看一下JVM記憶體結構

棧:存放區域性變數

堆:存放所有new出來的東西

方法區:被虛擬機器載入的類資訊、常量、靜態常量等。

程式計數器(和系統相關)

本地方法棧

堆記憶體就是GC管理的主要區域(知識點,重點)

然後JVM又把堆記憶體分三代,新生代,老年代,持久代。如下圖

  • 新生代:分為eden ,From Survivor TO Survivor

絕大多數剛建立的物件會被分配在Eden區,其中的大多數物件很快就會消亡。Eden區是連續的記憶體空間,因此在其上分配記憶體極快。(每次新生代的垃圾回收(又稱Minor GC)採用copy演算法,之後再講)

最初一次,當Eden區滿的時候,執行Minor GC,將消亡的物件清理掉,並將剩餘的物件複製到一個存活區From (此時,TO 是空白的,兩個Survivor總有一個是空白的);

下次Eden區滿了,再執行一次Minor GC,將消亡的物件清理掉,將存活的物件複製到TO 中,然後清空Eden

區同時也 From 中消亡的物件清理掉,將存活的物件也複製到TO 區,然後清空From區;之後,“From”“To”會交換他們的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名為ToSurvivor區域是空的。Minor GC會一直重複這樣的過程,直到“To”區被填滿,“To”區被填滿之後,會將所有物件移動到年老代中。

如果沒有填滿,當兩個存活區切換了幾次(每進行一次MinorGC,都會在存活的物件做一個標記,加1,當標記的值大於15HotSpot虛擬機器預設15次,用-XX:MaxTenuringThreshold

控制,進入老年代)之後,仍然存活的物件(其實只有一小部分,比如,我們自己定義的物件),將被複制到老年代。

  • 老年代

在新生代中經歷了N次垃圾回收後仍然存活的物件,就會被放到年老代,該區域中物件存活率高。老年代的垃圾回收(又稱Major GC)通常使用標記-清理標記-整理演算法。整堆包括新生代和老年代的垃圾回收稱為Full GCHotSpot VM裡,除了CMS之外,其它能收集老年代的GC都會同時收集整個GC堆,包括新生代)

當老年代的空間不足時,會觸發Major GC/Full GC,速度一般比Minor GC10倍以上。

  • 持久代(永久代)

JDK8之前的HotSpot實現中,類的元資料如方法資料、方法資訊(位元組碼,棧和變數大小)、執行時常量池、已確定的符號引用和虛方法表等被儲存在永久代中,32位預設永久代的大小為64M64位預設為85M,可以通過引數-XX:MaxPermSize進行設定。GC不會在主程式執行期對永久區域進行清理,這也導致了永久代的區域會隨著載入的Class的增多而脹滿,最終丟擲OOM異常。

所以虛擬機器團隊在JDK8HotSpot中,把永久代從Java堆中移除了,並把類的元資料直接儲存在本地記憶體區域(堆外記憶體),稱之為元空間。

元空間並不在虛擬機器中,而是使用本地記憶體。因此,預設情況下,元空間的大小僅受本地記憶體限制。類的元資料放入 native memory, 字串池和類的靜態變數放入java堆中. 這樣可以載入多少類的元資料就不再由MaxPermSize控制, 而由系統的實際可用空間來控制。

總結:

為了分代垃圾回收,Java堆記憶體分為3代:新生代,老年代和永久代

新的物件例項會優先分配在新生代,在經歷幾次Minor GC(預設15),還存活的會被移至老年代(某些大物件會直接在老年代分配)

Minor GC發生在新生代,當Eden區沒有足夠空間時,會發起一次Minor GC,將Eden區中的存活物件移至Survivor區。Major GC發生在老年代,當升到老年代的物件大於老年代剩餘空間時會發生Major GC

發生Major GC時使用者執行緒會暫停,會降低系統性能和吞吐量。

JVM的引數-Xmx-Xms用來設定Java堆記憶體的初始大小和最大值。依據個人經驗這個值的比例最好是1:1或者1:1.5。比如,你可以將-Xmx-Xms都設為1GB,或者-Xmx-Xms設為1.2GB1.8GB

Java中不能手動觸發GC,但可以用不同的引用類來輔助垃圾回收器工作