1. 程式人生 > >網際網路技術學習27———垃圾回收演算法+垃圾收集器

網際網路技術學習27———垃圾回收演算法+垃圾收集器

垃圾回收Garbage Collection,簡稱GC。GC中的垃圾指的是記憶體中不會再被使用的物件,而回收就是相當於把垃圾“倒掉”。垃圾回收有很多演算法:引用計數法、標記壓縮法、複製演算法、分代、分割槽。

在java堆中,新生代/老年代 =1/2 或1/3比較合適

垃圾回收演算法 

引用計演算法:這是一個比較古老而經典的垃圾收集演算法,其核心就是在物件被引用的時候計數器加1,而當引用失效時減1,但是這種方式存在嚴重的問題,無法處理迴圈引用的情況,還有就是每次進行加減操作是比較浪費系統性能。

標記清除法:分為標記和清除兩個階段,這種方式的弊端就是空間碎片問題。垃圾回收後的空間是不連續的。不連續的記憶體空間工作效率要低於連續的空間,且造成了記憶體的浪費。

標記壓縮法:標記壓縮法在標記清除法的基礎上做了優化,把存活的物件壓縮到記憶體的一端,而後進行垃圾清理

分代演算法:根據物件的特點把記憶體分為N塊,然後根據每個記憶體塊的特點使用不同的演算法。如Java中的新生代和老年代

分割槽演算法:主要就是將整個記憶體分成N多個小空間,每個小空間都可以獨立使用,這樣細粒度的控制一次回收多少個小空間和哪些小空間,而不是對整記憶體進行GC,從而提高效能,並減少GC停頓時間。

 

對於新生代和老年代來說,新生代回收的頻率很高,但是每次回收耗時短,而老年代gc頻率較低,但是每次耗時長,所以應該儘量減少老年代的GC。

老年代中物件已經比較穩定,經過多次GC仍未被回收,在GC過程中,清理的記憶體比較小,存活的物件佔比非常高,因此老年代使用標記壓縮法。

垃圾回收時的停頓現象

為了讓垃圾回收機高效的進行,大部分情況下,會要求系統進入一個停頓狀態(stop the world)。停頓的目的是終止所有應用執行緒,只有這樣系統才不會有新的垃圾產生,同時停頓保證了系統狀態在某個瞬間的一致性,也有益於更好的標記垃圾物件。

物件如何進入老年代
新建立的物件放置在新生代的eden區,如果沒經歷過gc,改新建立的物件會一直放在eden區。經過一次gc後,進入新生代的s0或s1區,在新生代中物件每經歷過一次gc年齡就會增加1,在物件的年齡達到一定大小的時候,就會從新生代進入老年代,物件的年齡應該是該物件經歷過的gc次數決定的。虛擬機器提供了一個引數來控制信新生代物件的最大年齡,當超過這個年齡範圍就會晉升為老年代。另外,大物件即新生代eden區無法裝入時,也會直接進入老年代,JVM中存在相關引數可以設定物件大小超過指定對的大小之後,直接晉升為老年代。

-XX:MaxTenuringThresold 指定新生代物件經過多少次GC就進入老年代。預設情況下是15

-XX:PretenureSizeThreshold 指定不經過新生代直接進入老年代的物件大小

 

Test05.java

package com.jvmTest;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by BaiTianShi on 2018/9/24.
 */
public class Test05 {
    public static void main(String[] args) {
        Map<Integer,byte[]> map = new HashMap<>();
        for (int i = 0; i < 5 ; i++) {
            byte[] b = new byte[1024*1024];
            map.put(i,b);
        }
    }

}

引數設定:
-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000

控制檯列印:

上述程式碼中,設定了-XX:PretenureSizeThreshold=1000,即指定不經過新生代而直接晉升到老年代的物件大小為1000位元組,而程式碼中new byte[1024*1024] 的大小為1M>1000k,屬於大獨享直接放在老年代,因此老年代userd 5136k。

Test06.java

package com.jvmTest;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by BaiTianShi on 2018/9/24.
 */
public class Test06 {
    public static void main(String[] args) {

        Map<Integer,byte[]> map =new HashMap<>();
        for (int i = 0; i < 5*1024 ; i++) {
            byte[] b =new byte[1000];
            map.put(i,b);
        }
    }
}

JVM設定引數: -Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000

控制檯列印:

老年代僅僅使用了56k,這是因為虛擬機器對於體積不大的物件,會優先把資料分配到TLAB區域中,因此就失去了再老年代分配的機會。JVM中每個執行緒,預設是使用TLAB的。

TLAB

TLAB即Thread Local Allocation Buffer 即執行緒本地分配快取,每一個執行緒都會產生一個TLAB,是執行緒獨享的工作區域,JVM使用TLAB避免多執行緒衝突問題,提高物件的分配效率。TLAB空間一般不會太大,當大物件無法再TLAB分配至,會直接分配到堆上。

-XX:+UseTLAB 使用TLAB

-XX:-UseTLAB 禁用TLAB

-XX:+TLABSize 設定TLAB大小

-XX:TLABRefillWasteFraction 設定TLAB空間的單個物件的大小嗎,它是一個比例,預設為64,即物件大於整個空間的1/64時,則在堆中建立此物件。

-XX:+PrintTLAB 列印TLAB資訊

-XX:ResizeTLAB 自動調整TLABRefillWasteFraction閾值。

一個物件的建立流程

一個物件建立在什麼位置,JVM會有一個比價細節的流程,根據資料的大小,引數的設定,決定如何建立分配,以及其位置

 

垃圾收集器

在tomcat的catalina.sh中使用JAVA_OPT配置初始堆大小、最大堆大小

在JVM中,垃圾回收器有多種:序列垃圾回收器、並行垃圾回收器、CMS回收器(主流)、G1回收器

序列回收器:使用單執行緒進行垃圾回收的垃圾回收器,對於並行能力較弱的計算機來說,序列回收器的專注性和獨佔性往往有更好的效能表現。

+XX:UseSerialGC  設定新生代序列回收器和老年代序列回收器

並行回收器(新生代ParNew回收器):在序列回收器上做了改進,可以使用多個執行緒同時進行垃圾回收,對於計算能力強的計算機而言,可以有效的縮短垃圾回收所需要的時間,ParNew回收器是一個工作在新生代的垃圾回收器,它只是簡單的將序列回收器多執行緒話,它的回收策略和演算法與序列回收器一樣。

-XX:+UseParNewGC 使用新生代ParNew垃圾回收器

-XX:ParallelGCThreads 指定ParNew回收器工作時的執行緒數量,一般最好和計算機cpu核數相當,避免過多執行緒影響效能。

並行回收器(新生代ParallelGC):新生代ParalleGC回收器,它是使用了複製演算法的回收器,也就是多執行緒獨佔形式的收集器,它有個非常重要的特點,即非常關注系統的吞吐量。提供了2個非常關鍵的引數控制系統的吞吐量。

-XX:MaxGCPauseMillis 設定最大垃圾收集停頓時間,可將虛擬機器在GC的停頓時間控制在MaxGCPauseMillis範圍內。如果希望減少GC停頓時間,可將MaxGCPauseMillis設定的很小,但是會導致GC頻繁,從而增加GC總時間,降低了吞吐量,所以需要根據實際情況設定該值。

-XX:GCTimeRatio 設定吞吐量大小,它是一個0-100之間的數,預設情況下是99,那麼系統將花費不超過1/(1+n)的時間用於垃圾回收。

-XX:UseAdaptiveSizePolicy 開啟自適應模式,在這種模式下,新生代大小、eden和from或to的比例,以及晉升老年代的物件年齡引數都會被自動調整,以達到堆大小、吞吐量、停頓時間的平衡點

並行回收器(老年代ParallelOldGC):老年代ParallelOldGC回收器也是一種多執行緒回收器,和新生代的ParallelGC回收器一樣,也是一種關注吞吐量的回收器,使用了標記壓縮演算法進行實現。

-XX:+ParallelOldGC 設定使用老年代ParallelOldGC收集器

-XX:ParalleGCThreads 設定垃圾收集時的執行緒數量

CMS回收器(主流):全稱Concurrent Mark Sweep 意思為併發標記清除,使用的是標記清除演算法,主要關注系統停頓時。且適用於老年代。

-XX:+UseConcMarkSweepGC 使用cms垃圾回收器

-XX:ConcGCThreads 設定併發執行緒數

CMS並不是獨佔的垃圾回收器,在其進行垃圾回收的過程,程式仍然在工作(其實remark標記階段仍然需要stop the world),又會有新的垃圾不斷產生,所以在使用CMS的過程中要保證應用程式的記憶體足夠可用,CMS不會等到應用程式飽和的時候才去回收垃圾,而是在某一個閾值的時候就開始回收,回收閾值可以使用指定的引數進行設定-XX:CMSInitiatingOccupancyFraction來指定,預設為68,即當老年代中的使用空間達到68的時候,會執行CMS回收。如果記憶體使用增長過快,在CMS執行過程中,已經出現了記憶體不足的情況,此時CMS回收就會失敗,虛擬機器將啟動老年代序列回收器進行垃圾回收,這會導致應用程式中斷,直到垃圾回收完成後才會正常工作,這個過程GC停頓時間可能較長,所以-XX:CMSInitiatingOccupancyFraction的設定要根據實際的情況來決定。

標記清除法有個缺點就是存在碎片的問題,在CMS中存在相關的引數如下:

-XX:+UseCMSCompactAtFullCollection  設定在CMS回收完成之後進行一次碎片整理

-XX:CMSFullGCBeforeCompaction 設定進行多少次CMS回收之後,對記憶體進行一次壓縮

 

G1回收器:全稱Garbage-First,它是在jdk1.7中提出的垃圾回收器,長期目標是為了取代CMS回收器,G1回收器擁有獨特的垃圾回收策略,G1屬於分代垃圾回收器,區分新生代和老年代,依然有eden和from/to區,它並不要求整個eden區或者新生代、老年代空間都連續,它使用了分割槽演算法。

並行性:G1回收期間可以多執行緒同時工作

併發性:G1擁有與應用程式交替執行能力,部分工作可以與應用程式同時執行,在整個GC期間不會完全阻塞應用程式

分代GC:G1是一個分代收集器,但是它兼顧新生代和老年代一起工作,之前的垃圾收集器他們或者在新生代工作,或者在老年代工作,這是一個很大的不同。

空間整理:G1在回收過程中,不會像CMS那樣在若干次GC之後需要進行碎片整理,G1採用了有效複製物件的方式,減少空間碎片。

可預見性:由於分割槽的原因,G1可以只選擇部分割槽域進行回收,縮小了回收的範圍,提高了效能。

-XX:+UseG1GC  使用G1收集器

-XX:MaxGCPauseMillis  指定最大停頓時間

-XX:ParallelGCThreads  設定並行回收的執行緒數量