一. 如何找到一個垃圾?
1) 引用計數算法:給對象添加一個引用計數器,有一次引用,計數器值就加1;當引用失效時,計數器值就減1。很多流程的編程語言例如Python都使用這種方法管理內存,但是主流的Java虛擬機沒有選用它,主要原因是它很難解決對象之間相互循環引用的問題。
2) 根可達性分析算法:因爲引用計數算法無法解決對象之間相互循環引用的問題,繼而引出了這個算法。思想是以GC Roots作爲起始點開始向下搜索,所走過的路徑成爲引用鏈,當一個對象到GC Roots沒有任何引用鏈時,則這個對象是不可用的,就是垃圾。如圖所示:
二. 找到一個垃圾後,如何清除它?
1) Mark-Sweep(標記清除):先標記出可回收的對象,然後清除。如下黑色部分爲可回收對象,灰色部分爲存活對象,綠色部分爲未使用對象。
它的主要不足有兩個:
1. 標記和清除兩個過程的效率都不高
2. 另一個是空間的問題,標記清除之後產生了大量不連續的內存碎片,碎片會導致以後需要分配較大對象時,無法找到足夠的連續內存,繼而提前觸發另一次垃圾收集動作。
2) Copying(拷貝):將內存劃分爲兩塊,將存活對象全部copy到下面的區域,然後把上面的全部清除。
新生代中的Survivor1和Survivor2就是這樣的。
缺點:浪費內存
3) Mark-Compact(標記壓縮或者標記整理):既不想碎片化,又不想浪費內存,就先將回收的對象標記起來,然後一邊回收,一邊把存活對象向一端移動。
缺點:效率偏低
三. 內存分配和回收的過程是什麼樣子的
內存分配與回收策略如下圖:
名詞解釋:
YGC ==Young GC == Minor GC 新生代回收
Major GC 老年代回收
Full GC 新生代和老年代一塊回收
圖示解釋:
1) new出一個對象,首先嚐試在棧上分配,如果能直接分配下,就在棧上分配。對象在棧裏一旦pop,對象就沒了。(棧上分配好處:不需要GC介入,對象用完就可以消失,但是棧的空間非常小)
2) 如果棧上分配不下,並且對象比較大,會直接進入老年代,經歷Major GC/Full GC的回收,然後消亡。
3) 如果對象不夠大,先在線程本地分配 (TLAB: Thread Local Allocation Buffer),如果TLAB滿了,直接進入Eden,不管進不進入TLAB,最終都會進入Eden區域,進入Eden區域會競爭資源,出現線程同步,特別消耗資源,因此Hotspot做了優化,Eden區域爲每個線程都分配了一小塊區域,這樣就不會每個線程都去搶奪資源。
4) 在Eden區域,對象如果能被GC清除,直接就消亡了
5) 在Eden區域,如果回收不掉,進入Survivor1區域,然後再次回收,年齡加1,如果年齡到了(CMS默認是6歲,其他默認都是15歲),然後進入老年代,如果年齡沒到,進入Survivor2區域,然後再次回收,循環往復,Survivor1和Survivor2還會來回替換(因爲有個copy垃圾回收算法),直到進入老年代。
四. 垃圾收集器(10種)
前面六個是分代模型,後面四個是不分代模型。
1. 垃圾收集器的發展路線,是隨着內存越來越大的過程而演進的。
從分代算法演化到不分代算法
Serial算法 幾十兆
Parallel算法 幾個G
CMS 幾十個G 承上啓下,開始併發回收
-三色標記-
2. JDK誕生,Serial追隨 提高效率,誕生了PS,爲了配合CMS,誕生了PN,CMS是1.4版本後期引入,CMS是里程碑式的GC,它開啓了併發回收的過程,但是CMS毛病較多,併發垃圾回收的原因是因爲無法忍受Serial回收的STW。
3. Serial 收集器 年輕代 串行回收
特點:在回收垃圾時,必須暫停其他所有的工作線程,知道它收集結束
4. PS 年輕代 並行回收
5. ParNew 年輕代 配合CMS的並行回收,它和Parallel Scavenge一樣,區別就是它配合CMS使用。
6. Serial Old 收集器
7. Parallel Old
8. CMS 全稱 ConcurrentMarkSweep 老年代 併發的,垃圾回收和應用程序同時進行,降低STW的時間(200ms),CMS問題很多,所以沒有一個版本默認是CMS,只能手動設定。CMS既然是MarkSweep,就一定會有碎片化的問題,碎片達到一定的程度,CMS老年代分配對象分配不下的時候,使用Serial Old進行老年代回收。(面試重災區)
主要有四個過程:
初始標記、併發標記、重新標記、併發清理
CMS的缺點是:產生了浮動垃圾,並且使用Serial Old來清理整個老年代,這是CMS設計的缺陷;但是如果CMS做好調優,支持的內存要比Parallel Old大的多。
想象一下:
PS + PO -> 加內存 換垃圾回收器 -> PN + CMS + Serial Old (幾個小時-幾天的STW) ,幾十個G 的內存,單線程的回收 -> G1 + Full GC 幾十個G -> 上T內存的服務器 ZGC
CMS併發標記採用的是: 三色標記法 + Incremental Update
9. G1 垃圾回收器 (200ms - 10 ms)
算法:三色標記 + SATB
由於越來越多的內存需要回收,必然會產生STW,所以G1應運而生。
邏輯分代,物理不分代。
10. ZGC (10ms - 1ms ) PK C++
算法:ColoredPointers + LoadBarrier
11. Shenandoah
算法:ColorPointers + WriteBarrier
CMS中新生代的默認年齡是6,PS/PO中新生代的默認年齡是15,進入老年代,可以通過參數:-XX:MaxTenuringThreshold配置
jdk1.0自帶的Serial和Serial Old,現在用的最多的是Parallel Scavenge和Parallel Old,調優用的是ParNew和CMS,jdk1.8用G1也沒有問題
上圖:前面六種,一般都是新生代和老年代配合使用。ParNew和CMS,Serial和Serial Old,Parallel Scavenge和Parallel Old。
jdk1.8默認是Parallel Scavenge和Parallel Old,不管是單線程Serial還是並行Parallel,只要人多,都會出現問題,所以就有了承前啓後的ParNew和CMS。