GC的本質: 從GC Roots
開始,沿着引用鏈找到全部的能夠到達的對象(reachable objects
),並把它們標記爲活動對象(alived objects
),這個過程叫可達性分析。java
垃圾回收器跟蹤全部仍在使用的對象,並將其他對象標記爲垃圾。程序員
正如咱們所看到的,很容易忘記釋放內存。內存泄漏是常見的問題。只有修改代碼才能真正解決問題。所以,更好的方法是自動回收未使用的內存,徹底消除人爲錯誤的可能性。這種自動化稱爲垃圾收集(簡稱GC)算法
引用計數(Reference Counting
)編程
綠色的雲代表程序員指向的對象仍在使用中。 從技術上講,這些多是諸如當前正在執行的方法中的局部變量或靜態變量之類的東西編程語言
藍色圓圈是內存中的活動對象,其中的數字表示其引用計數。性能
灰色圓圈是還沒有從仍在顯式使用的任何對象中引用的對象(這些對象由綠雲直接引用)。 所以,灰色物體是垃圾,能夠由垃圾收集器清理spa
引用計數的致命缺點—-循環引用線程
因爲循環引用,其引用計數不爲零,紅色對象其實是應用程序不使用的垃圾。可是因爲引用計數的限制,仍然存在內存泄漏3d
標記清除(Mark and Sweep)code
從一組預先定義好的根節點(GC root
)開始,把全部可達的對象都打上標記,而後把不可達的對象(垃圾)所有清除掉。 JVM
用來跟蹤全部可訪問(活動)對象並確保不可訪問對象聲明的內存能夠重用的方法稱爲標記和清除算法。 標記清除主要分爲兩步:
GC Roots
開始遍歷全部可訪問的對象,並在本機內存中保存這些對象的有關記錄碎片化問題
長期工做的內存會出現碎片化,總可用空間仍然足夠,可是沒法分配大對象
每當進行垃圾清理時,JVM
必須確保這些垃圾對象所佔的內存能夠被重用,可是這可能會致使內存碎片化問題,這和磁盤碎片相似,會致使兩個問題:
JVM
會分配連續的內存,所以若是碎片化到達某種程度,致使沒有任何單獨的空閒碎片小都不足以容納新建立的對象,則會發生分配錯誤Write
操做,會由於很難找到下一個足夠大的內存塊,而變的更加耗時爲了不這些問題,JVM會確保碎片化不會到失控的地步,由於垃圾回收不只要把垃圾清除掉,還要作內存碎片整理,把剩下的非垃圾對象所佔的內存壓縮的更緊密(內存地址更連續),方便後續分配較大的對象。
研究代表,絕大多數對象的生命週期都很短(朝生夕死)
有了這些獨立的、單獨的可清理區域,就可使用大量不一樣的算法,這些算法在提升GC性能方面取得了長足的進步
Eden
: 伊甸園Survivor
: 倖存區任何新建立的對象都會先進入Eden
(上帝創造亞當夏娃,讓他們結爲夫妻生活在伊甸園), 而後經歷了一場洪水或災難以後,進入了Survivor
(倖存區),當一個對象經歷過不少災難(GC
)後,它會變得很資深,就進入了Tenured
(老年代)
Eden
(伊甸園)
Java中新建立的對象會分配到Eden中。因爲會有多個線程同時建立多個對象的場景,所以Eden 進一步分爲一個或多個駐留在Eden空間中的Thread Local Allocation Buffer
簡稱(TLAB
)。 這些緩衝區容許JVM在相應的TLAB中直接在線程內分配大多數對象,從而避免了和其它線程的同步問題。
當TLAB
中沒有足夠的空間時,該對象會被分配到Eden
的共享空間(如上圖中的Common area
), 此時會觸發Young GC
(年輕代的垃圾回收)來釋放更多的空間,若是垃圾回收後仍沒有足夠的空間,這些對象會被放入 老年代中。
當Eden
發生GC
時,GC
會從Gc Roots
開始,沿着引用鏈訪問全部可達對象,並將其標記爲活動對象。
Marking phase
(標記階段)完成後,Eden
中的全部活動對象會被複制到一個Survivor
中,此時整個Eden
被認爲是空的,能夠用來從新分配對象。
這種方法叫作"Mark And Copy
": 標記活的對象,而後把他們移動到Survivor
中去。
Survivor
(倖存區)
Young GC
發生時,整個年輕代中的對象會進入其中一個Survivor
區緊挨着Eden
的是兩個叫作fromandto
的生存空間。須要注意的是,兩個Survivor
中的一個老是空的。
在兩個Survivor
之間複製活動對象的過程會重複幾回,直到某些對象被認爲已經足夠成熟, 就能夠晉升到 Tenured
(老年代)
如何判斷活動對象是否"足夠老"到能夠晉升到Tenured
?
在每一代對象完成一個GC
以後,那些仍然活着的對象的年齡就會增長。每當年齡超過某個***閾值***時,對象將被提高到老年代
實際的tenuring
閾值由JVM動態調整,但指定-XX:+maxtenuringthreshold
設置上限。設置- xx:+MaxTenuringThreshold=0
會致使活動對象由Eden
直接晉升到老年區,無需在Survivor
之間來回複製活動對象。默認狀況下,現代JVM上的這個閾值設置爲15個GC週期。這也是HotSpot
中的最大值。
Java8以前:永久代
是堆得一部分
存儲類數據、字符串常量
java.lang.OutOfMemoryError:Permgenspace
java -XX:MaxPermSize=256m com.mycompany.MyApplication
Java8以後:元空間
不是堆得一部分
除非特殊指定,不然空間大小沒有上限
java.lang.OutOfMemoryError:Metaspace
java -XX:MaxMetaspaceSize=256m com.mycompany.MyApplication
Minor GC/Young GC
JVM
沒法爲新對象分配空間時觸發,例如Eden
變得滿了。所以,分配率越高,發生Minor GC
的頻率就越高Major GC vs Full GC
標記階段(Marking Reachable Objects)
JVM中使用的全部現代GC算法都是從找到全部alive objects(活動對象)開始。
下面這張圖很好的解釋了JVM的內存分佈(綠色雲表明GC Roots, 藍色圈表明可達對象,灰色圈表明不可達對象):
首先GC定義了一些指定的對象做爲GC Roots:
而後從GC Roots開始沿着引用鏈作可達性分析,找到全部可達對象並將它們標記爲活動對象(alive objects)。
標記階段有幾個重要方面須要注意:
清除階段(Removing Unused Objects)
不一樣的GC算法對Unused Objects對象的刪除有所不一樣,但能夠分爲三類:
Sweep(Mark and Sweep): 標記-清除
Compact(Mark-Sweep-Compact):標記-清除-壓縮
老年代中使用
Copy(Mark-and-Copy): 標記-清除-複製
年輕代中使用(Eden—>Survivor)
因此一個完整的GC過程以下: