死磕Java虛擬機,帶你走進性能調優理論

一. 如何找到一個垃圾?

1) 引用計數算法:給對象添加一個引用計數器,有一次引用,計數器值就加1;當引用失效時,計數器值就減1。很多流程的編程語言例如Python都使用這種方法管理內存,但是主流的Java虛擬機沒有選用它,主要原因是它很難解決對象之間相互循環引用的問題。

2) 根可達性分析算法:因爲引用計數算法無法解決對象之間相互循環引用的問題,繼而引出了這個算法。思想是以GC Roots作爲起始點開始向下搜索,所走過的路徑成爲引用鏈,當一個對象到GC Roots沒有任何引用鏈時,則這個對象是不可用的,就是垃圾。如圖所示:

死磕Java虛擬機,帶你走進性能調優理論

二. 找到一個垃圾後,如何清除它?

1) Mark-Sweep(標記清除):先標記出可回收的對象,然後清除。如下黑色部分爲可回收對象,灰色部分爲存活對象,綠色部分爲未使用對象。

死磕Java虛擬機,帶你走進性能調優理論

它的主要不足有兩個:

1. 標記和清除兩個過程的效率都不高

2. 另一個是空間的問題,標記清除之後產生了大量不連續的內存碎片,碎片會導致以後需要分配較大對象時,無法找到足夠的連續內存,繼而提前觸發另一次垃圾收集動作。

2) Copying(拷貝):將內存劃分爲兩塊,將存活對象全部copy到下面的區域,然後把上面的全部清除。

死磕Java虛擬機,帶你走進性能調優理論

新生代中的Survivor1和Survivor2就是這樣的。

缺點:浪費內存

3) Mark-Compact(標記壓縮或者標記整理):既不想碎片化,又不想浪費內存,就先將回收的對象標記起來,然後一邊回收,一邊把存活對象向一端移動。

死磕Java虛擬機,帶你走進性能調優理論

缺點:效率偏低

三. 內存分配和回收的過程是什麼樣子的

死磕Java虛擬機,帶你走進性能調優理論

內存分配與回收策略如下圖:

死磕Java虛擬機,帶你走進性能調優理論

名詞解釋:

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種)

死磕Java虛擬機,帶你走進性能調優理論

前面六個是分代模型,後面四個是不分代模型。

1. 垃圾收集器的發展路線,是隨着內存越來越大的過程而演進的。

從分代算法演化到不分代算法

Serial算法 幾十兆

Parallel算法 幾個G

CMS 幾十個G 承上啓下,開始併發回收

-三色標記-

2. JDK誕生,Serial追隨 提高效率,誕生了PS,爲了配合CMS,誕生了PN,CMS是1.4版本後期引入,CMS是里程碑式的GC,它開啓了併發回收的過程,但是CMS毛病較多,併發垃圾回收的原因是因爲無法忍受Serial回收的STW。

3. Serial 收集器 年輕代 串行回收

死磕Java虛擬機,帶你走進性能調優理論

特點:在回收垃圾時,必須暫停其他所有的工作線程,知道它收集結束

4. PS 年輕代 並行回收

死磕Java虛擬機,帶你走進性能調優理論

5. ParNew 年輕代 配合CMS的並行回收,它和Parallel Scavenge一樣,區別就是它配合CMS使用。

死磕Java虛擬機,帶你走進性能調優理論

6. Serial Old 收集器

死磕Java虛擬機,帶你走進性能調優理論

7. Parallel Old

8. CMS 全稱 ConcurrentMarkSweep 老年代 併發的,垃圾回收和應用程序同時進行,降低STW的時間(200ms),CMS問題很多,所以沒有一個版本默認是CMS,只能手動設定。CMS既然是MarkSweep,就一定會有碎片化的問題,碎片達到一定的程度,CMS老年代分配對象分配不下的時候,使用Serial Old進行老年代回收。(面試重災區)

死磕Java虛擬機,帶你走進性能調優理論

主要有四個過程:

初始標記、併發標記、重新標記、併發清理

死磕Java虛擬機,帶你走進性能調優理論死磕Java虛擬機,帶你走進性能調優理論

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應運而生。

邏輯分代,物理不分代。

死磕Java虛擬機,帶你走進性能調優理論

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。

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