Java的垃圾收集器


垃圾收集器是垃圾回收算法(標記-清除算法、復制算法、標記-整理算法、火車算法)的具體實現,不同商家、不同版本的JVM所提供的垃圾收集器可能會有很在差別,本文主要介紹HotSpot虛擬機中的垃圾收集器。

下圖是java8 HotSpot虛擬機所有的垃圾收集器,連接先代表可也配合使用的組合,G1是對整個堆進行收集

 

用於新生代的收集器有:Serial、ParNew、Paraller Scavenge

用於老年代的收集器有:CMS(Concurrent Mark Sweep)、Serial Old、Parallel Old

G1垃圾收集器相對比其他收集器而言,最大的區別在於它取消了年輕代、老年代的物理划分,取而代之的是將堆划分為若干個區域(Region),這些區域中包含了有邏輯上的年輕代、老年代區域。

收集器的分類

 

 

  • 串行垃圾收集器:Serial、Serial Old
  • 並行垃圾收集器:ParNew、Parallel Old、Paraller Scavenge
  • 並發垃圾收集器:CMS(Concurrent Mark Sweep)
  • G1:G1的話比較特殊一點,接下來進行介紹

如何查看當前java默認使用的是哪種垃圾收集器?

java -XX:+PrintCommandLineFlags -version

 

 

 UseParallelGC代表新生代默認使用的是Paraller Scavenge進行垃圾收集的,而老年代則使用是Parallel Old進行垃圾收集的

當然也可以使用IDEA進行測試,准備測試代碼,目的是查看GC收集的信息

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Random;

public class GCTest {
    public static void main(String[] args) throws InterruptedException {
        List<Object> list = new ArrayList<Object>(); while(true)

        {
            int sleep = new Random().nextInt(100);
            if (System.currentTimeMillis() % 2 == 0) {
                list.clear();
            } else {
                for (int i = 0; i < 10000; i++) {
                    Properties properties = new Properties();
                    properties.put("key_" + i, "value_" + System.currentTimeMillis() + i);
                    list.add(properties);
                }
            }
            Thread.sleep(sleep);
        }

    }
}

在程序啟動之前設置VM options屬性

 

-XX:+PrintGCDetails -Xms16m -Xmx16m

 啟動測試,觀察控制台

 

 

 新生代和老年的所使用的垃圾收集器如上圖

串行垃圾收集器

串行垃圾收集器,是指使用單線程進行垃圾回收,垃圾回收時,只有一個線程在工作,並且java應用中的所有線程都要暫停,等待垃圾回收的完成。這種現象稱之為STW(Stop-The-World)。
對於交互性較強的應用而言,這種垃圾收集器是不能夠接受的。一般在Javaweb應用中是不會采用該收集器的。
設置為串行垃圾收集器
-XX:+UseSerialGC -XX:+PrintGCDetails -Xms16m -Xmx16m

控制台打印信息

 

 可以看到垃圾收集已經變為DefNew和Tenured的的組合進行了,也就是Serial和Serial Old的組合

 

 並行垃圾收集器

也就是JAVA8中默認的垃圾收集器,但是還有一個新生代的選擇,那就是Par New,

ParNew 和Parallel Scavenge的不同之處

Parallel Scavenge收集器的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是盡可能地縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標則是達到一個可控制的吞吐量(Throughput)。所謂吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量 = 運行用戶代碼時間 /(運行用戶代碼時間 + 垃圾收集時間),虛擬機總共運行了100分鍾,其中垃圾收集花掉1分鍾,那吞吐量就是99%。

 

 

 

設置為ParNew進行新生代的垃圾回收

-XX:+UseParNewGC -XX:+PrintGCDetails -Xms16m -Xmx16m

通過-XX:+UseParNewGC參數設置年輕代使用ParNew回收器,老年代使用的依然是串行收集器
ParallelGC收集器
ParallelGC收集器工作機制和ParNewGC收集器一樣,只是在此基礎之上,新增了兩個和系統吞吐量相關的參數,使得其使用起來更加的靈活和高效。
相關參數如下:
  -XX:+UseParallelGC
    年輕代使用ParallelGC垃圾回收器,老年代使用串行回收器。
  -XX:+UseParallelOldGC
    年輕代使用ParallelGC垃圾回收器,老年代使用ParallelOldGC垃圾回收器。
  -XX:MaxGCPauseMillis
    設置最大的垃圾收集時的停頓時間,單位為毫秒需要注意的時,ParallelGC為了達到設置的停頓時間,可能會調整堆大小或其他的參數,如果堆的大小設置的較小,就會導致GC工作變得很頻繁,反而可能會影響到性能。該參數使用需謹慎。
  -XX:GCTimeRatio
    設置垃圾回收時間占程序運行時間的百分比,公式為1/(1+n)。它的值為0~100之間的數字,默認值為99,也就是垃圾回收時間不能超過1%
  -XX:UseAdaptiveSizePolicy
    自適應GC模式,垃圾回收器將自動調整年輕代、老年代等參數,達到吞吐量、堆大小、停頓時間之間的平衡。一般用於,手動調整參數比較困難的場景,讓收集器自動進行調整。

並發垃圾收集器

CMS垃圾收集器

CMS全稱 Concurrent Mark Sweep,是一款並發的、使用標記-清除算法的垃圾回收器,該回收器是針對老年代垃圾回收的,通過參數-XX:+UseConcMarkSweepGC進行設置。
CMS垃圾回收器的執行過程如下: 
  1. 初始化標記(CMS-initial-mark) ,標記root,會導致stw;
  2. 並發標記(CMS-concurrent-mark),與用戶線程同時運行;
  3. 預清理(CMS-concurrent-preclean),與用戶線程同時運行;
  4. 重新標記(CMS-remark) ,會導致stw;
  5. 並發清除(CMS-concurrent-sweep),與用戶線程同時運行;
  6. 調整堆大小,設置CMS在清理之后進行內存壓縮,目的是清理內存中的碎片;
  7. 並發重置狀態等待下次CMS的觸發(CMS-concurrent-reset),與用戶線程同時運行;

 

 

 

設置以CMS方式進行垃圾收集

-XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xms16m -Xmx16m

 

G1垃圾收集器

G1垃圾收集器是在jdk1.7中正式使用的全新的垃圾收集器,oracle官方計划在jdk9中將G1變成默認的垃圾收集器,以替代CMS。

 

 

 原理

  G1垃圾收集器相對比其他收集器而言,最大的區別在於它取消了年輕代、老年代的物理划分,取而代之的是將堆划分為若干個區域(Region),這些區域中包含了有邏輯上的年輕代、老年代區域。
  這樣做的好處就是,我們再也不用單獨的空間對每個代進行設置了,不用擔心每個代內存是否足夠。

 

 

  在G1划分的區域中,年輕代的垃圾收集依然采用暫停所有應用線程的方式,將存活對象拷貝到老年代或者Survivor空間,G1收集器通過將對象從一個區域復制到另外一個區域,完成了清理工作。
這就意味着,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有cms內存碎片問題的存在了。在G1中,有一種特殊的區域,叫Humongous區域。
  • 如果一個對象占用的空間超過了分區容量50%以上,G1收集器就認為這是一個巨型對象。
  • 這些巨型對象,默認直接會被分配在老年代,但是如果它是一個短期存在的巨型對象,就會對垃圾收集器造成負面影響。
  • 為了解決這個問題,G1划分了一個Humongous區,它用來專門存放巨型對象。如果一個H區裝不下一個巨型對象,那么G1會尋找連續的H分區來存儲。為了能找到連續的H區,有時候不得不啟動Full GC。

Young GC

Young GC主要是對Eden區進行GC,它在Eden空間耗盡時會被觸發。
  • Eden空間的數據移動到Survivor空間中,如果Survivor空間不夠,Eden空間的部分數據會直接晉升到年老代空間。
  • Survivor區的數據移動到新的Survivor區中,也有部分數據晉升到老年代空間中。最終Eden空間的數據為空,GC停止工作,應用線程繼續執行。

 

 

 

 Rembered Set

 

在GC年輕代的對象時,我們如何找到年輕代中對象的根對象呢?
  根對象可能是在年輕代中,也可以在老年代中,那么老年代中的所有對象都是根么?如果全量掃描老年代,那么這樣掃描下來會耗費大量的時間。於是,G1引進了RSet的概念。它的全稱是Remembered Set,其作用是跟蹤指向某個堆內的對象引用。
  無論G1還是其他分代收集器,JVM都是使用Remembered Set來避免全局掃描

 

 

每個Region初始化時,會初始化一個RSet,該集合用來記錄並跟蹤其它Region指向該Region中對象的引用,每個Region默認按照512Kb划分成多個Card,所以RSet需要記錄的東西應該是 xx Region的 xx Card。
 

Mixed GC 

  當越來越多的對象晉升到老年代old region時,為了避免堆內存被耗盡,虛擬機會觸發一個混合的垃圾收集器,即Mixed GC,該算法並不是一個Old GC,除了回收整個YoungRegion,還會回收一部分的Old Region,這里需要注意:是一部分老年代,而不是全部老年代,可以選擇哪些old region進行收集,從而可以對垃圾回收的耗時時間進行控制。也要注意的是Mixed GC 並不是 Full GC。
  
  MixedGC什么時候觸發?
由參數 -XX:InitiatingHeapOccupancyPercent=n 決定。默認:45%,該參數的意思是:當老年代大小占整個堆大小百分比達到該閥值時觸發。
  它的GC步驟分2步
    1. 全局並發標記(global concurrent marking)
    2. 拷貝存活對象(evacuation)
全局並發標記
1.初始標記(initial mark,STW)
  標記從根節點直接可達的對象,這個階段會執行一次年輕代GC,會產生全局停頓。
2.根區域掃描(root region scan)
  G1 GC 在初始標記的存活區掃描對老年代的引用,並標記被引用的對象。該階段與應用程序(非 STW)同時運行,並且只有完成該階段后,才能開始下一次 STW 年輕代垃圾回收。
3.並發標記(Concurrent Marking)
  G1 GC 在整個堆中查找可訪問的(存活的)對象。該階段與應用程序同時運行,可以被 STW 年輕代垃圾回收中斷。
4.重新標記(Remark,STW)
  該階段是 STW 回收,因為程序在運行,針對上一次的標記進行修正。
5.清除垃圾(Cleanup,STW)
  清點和重置標記狀態,該階段會STW,這個階段並不會實際上去做垃圾的收集,等待evacuation階段來回收。
拷貝存活對象
Evacuation階段是全暫停的。該階段把一部分Region里的活對象拷貝到另一部分Region中,從而實現垃圾的回收清理。
設置以G1方式進行垃圾回收
-XX:+UseG1GC -XX:+PrintGCDetails -Xms16m -Xmx16m

young GC

 

 Mixed GC

 

 G1的特點

(1)並行與並發

      能充分利用多CPU、多核環境下的硬件優勢;可以並行來縮短"Stop The World"停頓時間;也可以並發讓垃圾收集與用戶程序同時進行;

(2)分代收集,收集范圍包括新生代和老年代    

      能獨立管理整個GC堆(新生代和老年代),而不需要與其他收集器搭配;能夠采用不同方式處理不同時期的對象;      雖然保留分代概念,但Java堆的內存布局有很大差別;將整個堆划分為多個大小相等的獨立區域(Region);新生代和老年代不再是物理隔離,它們都是一部分Region(不需要連續)的集合;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM