日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

(七)Java垃圾收集器详解

發(fā)布時(shí)間:2023/12/20 java 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 (七)Java垃圾收集器详解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

面試官問:Java垃圾收集器了解過多少,說一下 JVM 有哪些垃圾回收器?這些問題在你面試高級Java的時(shí)候經(jīng)常會問到。本篇文章結(jié)合著【深入理解Java虛擬機(jī)】一書當(dāng)中整理了本篇博客。

如果想要對收集器了解的更深,建議一點(diǎn)一點(diǎn)讀,如果想大概了解一下,只是為了面試問到可以簡單說一下,那么可以直接看我下方寫的總結(jié),總結(jié)相對來說內(nèi)容不是很多,但是每個(gè)收集器優(yōu)缺點(diǎn)都整理到了。

目錄

    • 一、概述
    • 二、Java垃圾收集器
      • 2.1、Serial收集器
      • 2.2、ParNew收集器
      • 2.3、Parallel Scavenge收集器
      • 2.4、Serial Old收集器
      • 2.5、Parallel Old收集器
      • 2.6、CMS收集器
      • 2.7、Garbage First收集器
    • 三、總結(jié)
      • 3.1、JVM默認(rèn)用的哪個(gè)收集器?
      • 3.2、Serial收集器
      • 3.3、ParNew收集器
      • 3.4、Parallel Scavenge收集器
      • 3.5、Serial Old收集器
      • 3.6、Parallel Old收集器
      • 3.7、CMS收集器
      • 3.8、G1收集器

JVM的知識是連貫性的,如果你連Java內(nèi)存分布,以及垃圾回收相關(guān)知識都不知道,建議您不要讀這篇文章,讀起來會讓你失去學(xué)習(xí)的興致。可以先去讀讀我前面所寫的JVM相關(guān)知識。

一、概述

如果說收集算法是內(nèi)存回收的方法論,那垃圾收集器就是內(nèi)存回收的實(shí)踐者。

Java垃圾收集器這個(gè)不學(xué)就不能工作嗎?答案是不是,這個(gè)代表你掌握J(rèn)VM的深度。Java離開JVM就不行嗎,答案是肯定的,這個(gè)問就好似程序員離開電腦能行嗎,之前我是深深體會到JVM不學(xué)好,會吃多大的虧。項(xiàng)目內(nèi)存出現(xiàn)瓶頸問題根本無從下手,這都是需要依靠這些知識的積累,才能全方面的去解決JVM優(yōu)化問題。

我們都知道Java默認(rèn)的虛擬機(jī)類型是HotSpot虛擬機(jī),HotSpot虛擬機(jī)有多種不同的收集器,既然實(shí)現(xiàn)這么多種,意味著每一種都有每一種的作用。針對這一點(diǎn),我們進(jìn)行深入學(xué)習(xí)Java垃圾收集器。

垃圾收集器是干什么的?為什么要了解他呢?

很多人其實(shí)對這塊根本不理解,只知道面試的時(shí)候會經(jīng)常問垃圾回收算法,而且還知道有三種,標(biāo)記清除法、標(biāo)記復(fù)制法、標(biāo)記整理法,如果再問你,虛擬機(jī)用的哪個(gè)算法你還知道嗎?答案是,三種都用到了。

各款經(jīng)典收集器之間的關(guān)系 如圖所示。

七種作用于不同分代的收集器,如果兩個(gè)收集器之間存在連線,就說明它們可以搭配 使用,圖中收集器所處的區(qū)域,則表示它是屬于新生代收集器抑或是老年代收集器。接下來筆者將 逐一介紹這些收集器的目標(biāo)、特性、原理和使用場景,并重點(diǎn)分析CMS和G1這兩款相對復(fù)雜而又廣泛 使用的收集器,深入了解它們的部分運(yùn)作細(xì)節(jié)。

雖然垃圾收集器的技術(shù)在不斷進(jìn)步,但直到現(xiàn)在還沒有 最好的收集器出現(xiàn),更加不存在“萬能”的收集器,所以我們選擇的只是對具體應(yīng)用最合適的收集器。

二、Java垃圾收集器

2.1、Serial收集器

Serial收集器是最基礎(chǔ)、歷史最悠久的收集器,曾經(jīng)(在JDK 1.3.1之前)是HotSpot虛擬機(jī)新生代 收集器的唯一選擇。大家只看名字就能夠猜到,這個(gè)收集器是一個(gè)單線程工作的收集器,但它的“單線 程”的意義并不僅僅是說明它只會使用一個(gè)處理器或一條收集線程去完成垃圾收集工作,更重要的是強(qiáng) 調(diào)在它進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程,直到它收集結(jié)束?!癝top The World”這個(gè)詞語也 許聽起來很酷,但這項(xiàng)工作是由虛擬機(jī)在后臺自動發(fā)起和自動完成的,在用戶不可知、不可控的情況 下把用戶的正常工作的線程全部停掉,這對很多應(yīng)用來說都是不能接受的。讀者不妨試想一下,要是 你的電腦每運(yùn)行一個(gè)小時(shí)就會暫停響應(yīng)五分鐘,你會有什么樣的心情?下圖示意了Serial/Serial Old收 集器的運(yùn)行過程。


為什么要停止所有用戶線程?

對于“Stop The World”帶給用戶的惡劣體驗(yàn),早期HotSpot虛擬機(jī)的設(shè)計(jì)者們表示完全理解,但也 同時(shí)表示非常委屈:因?yàn)槟阋M(jìn)行清理的時(shí)候,你正清理著,還有用戶線程在造著,這清理到什么時(shí)候是個(gè)頭。

從JDK 1.3開始,一直到現(xiàn)在最新的JDK 13,HotSpot虛擬機(jī)開發(fā)團(tuán)隊(duì)為消除或者降低用戶線程因 垃圾收集而導(dǎo)致停頓的努力一直持續(xù)進(jìn)行著,從Serial收集器到Parallel收集器,再到Concurrent Mark Sweep(CMS)和Garbage First(G1)收集器,最終至現(xiàn)在垃圾收集器的最前沿成果Shenandoah和ZGC 等,我們看到了一個(gè)個(gè)越來越構(gòu)思精巧,越來越優(yōu)秀,也越來越復(fù)雜的垃圾收集器不斷涌現(xiàn),用戶線 程的停頓時(shí)間在持續(xù)縮短。

迄今為止,Serial收集器依然是HotSpot虛擬機(jī)運(yùn)行在客戶端模式下的默認(rèn)新生 代收集器。

什么是Java客戶端?

客戶端就是指的的桌面可以直接點(diǎn)擊的應(yīng)用,有的會直接在客戶端里面集成JDK,用戶便可以點(diǎn)擊直接運(yùn)行,當(dāng)關(guān)閉應(yīng)用的時(shí)候,就相當(dāng)于JVM停止,點(diǎn)擊應(yīng)用的時(shí)候,JVM開始運(yùn)行,所以一般客戶端的JVM我們要的是單線程性價(jià)比最高的。

優(yōu)點(diǎn):

  • 簡單而高效(與其他收集器的單線程相比)
  • 它是所有收集器里額外內(nèi)存消耗(Memory Footprint)最小的
  • 對于單核處理 器或處理器核心數(shù)較少的環(huán)境來說,Serial收集器由于沒有線程交互的開銷,專心做垃圾收集自然可以 `獲得最高的單線程收集效率
  • 在用戶桌面的應(yīng)用場景以及近年來流行的部分微服務(wù)應(yīng)用中,分配給虛 擬機(jī)管理的內(nèi)存一般來說并不會特別大,收集幾十兆甚至一兩百兆的新生代,垃圾收集的停頓時(shí)間完全可以控制在十幾、幾十毫秒,最多一 百多毫秒以內(nèi),只要不是頻繁發(fā)生收集,這點(diǎn)停頓時(shí)間對許多用戶來說是完全可以接受的。所以,Serial收集器對于運(yùn)行在客戶端模式下的虛擬機(jī)來說是一個(gè)很好的選擇。

    2.2、ParNew收集器

    ParNew收集器實(shí)質(zhì)上是Serial收集器的多線程并行版本,除了同時(shí)使用多條線程進(jìn)行垃圾收集之 外,其余的行為包括Serial收集器可用的所有控制參數(shù)(例如:-XX:SurvivorRatio、-XX: PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、對象分配規(guī) 則、回收策略等都與Serial收集器完全一致,在實(shí)現(xiàn)上這兩種收集器也共用了相當(dāng)多的代碼。ParNew收 集器的工作過程如圖所示。

    ParNew在JDK7之前是服務(wù)端模式下的HotSpot虛擬機(jī) 首選的新生代收集 器,原因:除了Serial收集器外,目前只有它能與CMS 收集器配合工作。

    在JDK 5發(fā)布時(shí),HotSpot推出了一款在強(qiáng)交互應(yīng)用中幾乎可稱為具有劃時(shí)代意義的垃圾收集器 ——CMS收集器。這款收集器是HotSpot虛擬機(jī)中第一款真正意義上支持并發(fā)的垃圾收集器,它首次 實(shí)現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時(shí)工作。

    遺憾的是,CMS作為老年代的收集器,卻無法與JDK 1.4.0中已經(jīng)存在的新生代收集器Parallel Scavenge配合工作,所以在JDK 5中使用CMS來收集老年代的時(shí)候,新生代只能選擇ParNew或者 Serial收集器中的一個(gè)。ParNew收集器是激活CMS后(使用-XX:+UseConcMarkSweepGC選項(xiàng))的默 認(rèn)新生代收集器,也可以使用-XX:+/-UseParNewGC選項(xiàng)來強(qiáng)制指定或者禁用它。

    可以說直到CMS的出現(xiàn)才鞏固了ParNew的地位,但成也蕭何敗也蕭何,隨著垃圾收集器技術(shù)的不 斷改進(jìn),更先進(jìn)的G1收集器帶著CMS繼承者和替代者的光環(huán)登場。G1是一個(gè)面向全堆的收集器,不 再需要其他新生代收集器的配合工作。所以自JDK 9開始,ParNew加CMS收集器的組合就不再是官方 推薦的服務(wù)端模式下的收集器解決方案了。官方希望它能完全被G1所取代,甚至還取消了ParNew加 Serial Old以及Serial加CMS這兩組收集器組合的支持(其實(shí)原本也很少人這樣使用),并直接取消了- XX:+UseParNewGC參數(shù),這意味著ParNew和CMS從此只能互相搭配使用,再也沒有其他收集器能 夠和它們配合了。讀者也可以理解為從此以后,ParNew合并入CMS,成為它專門處理新生代的組成部 分。ParNew可以說是HotSpot虛擬機(jī)中第一款退出歷史舞臺的垃圾收集器。

    ParNew收集器在單核處理器的環(huán)境中絕對不會有比Serial收集器更好的效果,甚至由于存在線程 交互的開銷,該收集器在通過超線程(Hyper-Threading)技術(shù)實(shí)現(xiàn)的偽雙核處理器環(huán)境中都不能百分之百保證超越Serial收集器。當(dāng)然,隨著可以被使用的處理器核心數(shù)量的增加,ParNew對于垃圾收集時(shí) 系統(tǒng)資源的高效利用還是很有好處的。它默認(rèn)開啟的收集線程數(shù)與處理器核心數(shù)量相同,可以使用-XX:ParallelGCThreads參數(shù)來限制垃圾收集的線程數(shù)。

    注意 從ParNew收集器開始,后面還將會接觸到若干款涉及“并發(fā)”和“并行”概念的收集器。 在大家可能產(chǎn)生疑惑之前,有必要先解釋清楚這兩個(gè)名詞。并行和并發(fā)都是并發(fā)編程中的專業(yè)名詞, 在談?wù)摾占鞯纳舷挛恼Z境中,它們可以理解為:

    • 并行(Parallel):并行描述的是多條垃圾收集器線程之間的關(guān)系,說明同一時(shí)間有多條這樣的線 程在協(xié)同工作,通常默認(rèn)此時(shí)用戶線程是處于等待狀態(tài)。

    • 并發(fā)(Concurrent):并發(fā)描述的是垃圾收集器線程與用戶線程之間的關(guān)系,說明同一時(shí)間垃圾 收集器線程與用戶線程都在運(yùn)行。由于用戶線程并未被凍結(jié),所以程序仍然能響應(yīng)服務(wù)請求,但由于 垃圾收集器線程占用了一部分系統(tǒng)資源,此時(shí)應(yīng)用程序的處理的吞吐量將受到一定影響。

    Parallel Scavenge收集器及 后面提到的G1收集器等都沒有使用HotSpot中原本設(shè)計(jì)的垃圾收集器的分代框架,而選擇另外獨(dú)立實(shí) 現(xiàn)。Serial、ParNew收集器則共用了這部分的框架代碼,
    詳細(xì)可參考: https://blogs.oracle.com/jonthecollector/our_collectors。

    2.3、Parallel Scavenge收集器

    Parallel Scavenge收集器也是一款新生代收集器,它同樣是基于標(biāo)記-復(fù)制算法實(shí)現(xiàn)的收集器,也是 能夠并行收集的多線程收集器……Parallel Scavenge的諸多特性從表面上看和ParNew非常相似,那它有 什么特別之處呢?

    Parallel Scavenge收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同,CMS等收集器的關(guān)注點(diǎn)是盡可能 地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而Parallel Scavenge收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐 量(Throughput)。所謂吞吐量就是處理器用于運(yùn)行用戶代碼的時(shí)間與處理器總消耗時(shí)間的比值, 即

    可以的當(dāng)做是一個(gè)輸入和輸出的比值。


    如果虛擬機(jī)完成某個(gè)任務(wù),用戶代碼加上垃圾收集總共耗費(fèi)了100分鐘,其中垃圾收集花掉1分 鐘,那吞吐量就是99%。停頓時(shí)間越短就越適合需要與用戶交互或需要保證服務(wù)響應(yīng)質(zhì)量的程序,良 好的響應(yīng)速度能提升用戶體驗(yàn);而高吞吐量則可以最高效率地利用處理器資源,盡快完成程序的運(yùn)算 任務(wù),主要適合在后臺運(yùn)算而不需要太多交互的分析任務(wù)。

    Parallel Scavenge收集器提供了兩個(gè)參數(shù)用于精確控制吞吐量:

    • -XX:MaxGCPauseMillis :控制最大垃圾收集停頓時(shí)間
    • -XX:GCTimeRatio :設(shè)置吞吐量大小
    • -XX:+UseAdaptiveSizePolicy :這是一 個(gè)開關(guān)參數(shù),當(dāng)這個(gè)參數(shù)被激活之后,就不需要人工指定新生代的大小(-Xmn)、Eden與Survivor區(qū) 的比例(-XX:SurvivorRatio)、晉升老年代對象大小(-XX:PretenureSizeThreshold)等細(xì)節(jié)參數(shù) 了,虛擬機(jī)會根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí) 間或者最大的吞吐量。這種調(diào)節(jié)方式稱為垃圾收集的自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics)。

    -XX:MaxGCPauseMillis參數(shù)允許的值是一個(gè)大于0的毫秒數(shù),收集器將盡力保證內(nèi)存回收花費(fèi)的 時(shí)間不超過用戶設(shè)定值。不過大家不要異想天開地認(rèn)為如果把這個(gè)參數(shù)的值設(shè)置得更小一點(diǎn)就能使得 系統(tǒng)的垃圾收集速度變得更快,垃圾收集停頓時(shí)間縮短是以犧牲吞吐量和新生代空間為代價(jià)換取的: 系統(tǒng)把新生代調(diào)得小一些,收集300MB新生代肯定比收集500MB快,但這也直接導(dǎo)致垃圾收集發(fā)生得 更頻繁,原來10秒收集一次、每次停頓100毫秒,現(xiàn)在變成5秒收集一次、每次停頓70毫秒。停頓時(shí)間 的確在下降,但吞吐量也降下來了。

    -XX:GCTimeRatio參數(shù)的值則應(yīng)當(dāng)是一個(gè)大于0小于100的整數(shù),也就是垃圾收集時(shí)間占總時(shí)間的 比率,相當(dāng)于吞吐量的倒數(shù)。譬如把此參數(shù)設(shè)置為19,那允許的最大垃圾收集時(shí)間就占總時(shí)間的5% (即1/(1+19)),默認(rèn)值為99,即允許最大1%(即1/(1+99))的垃圾收集時(shí)間。

    優(yōu)點(diǎn):

    對于收集器運(yùn)作不太了解,手工優(yōu)化存在困難的話,使用Parallel Scavenge收集器配合自適應(yīng)調(diào)節(jié)策 略,把內(nèi)存管理的調(diào)優(yōu)任務(wù)交給虛擬機(jī)去完成也許是一個(gè)很不錯(cuò)的選擇。只需要把基本的內(nèi)存數(shù)據(jù)設(shè) 置好(如-Xmx設(shè)置最大堆),然后使用-XX:MaxGCPauseMillis參數(shù)(更關(guān)注最大停頓時(shí)間)或- XX:GCTimeRatio(更關(guān)注吞吐量)參數(shù)給虛擬機(jī)設(shè)立一個(gè)優(yōu)化目標(biāo),那具體細(xì)節(jié)參數(shù)的調(diào)節(jié)工作就 由虛擬機(jī)完成了。自適應(yīng)調(diào)節(jié)策略也是Parallel Scavenge收集器區(qū)別于ParNew收集器的一個(gè)重要特性。

    官方介紹:http://download.oracle.com/javase/1.5.0/docs/guide/vm/gc-ergonomics.html。

    2.4、Serial Old收集器

    Serial Old是Serial收集器的老年代版本,它同樣是一個(gè)單線程收集器,使用標(biāo)記-整理算法。這個(gè)收 集器的主要意義也是供客戶端模式下的HotSpot虛擬機(jī)使用。如果在服務(wù)端模式下,它也可能有兩種用 途:

    • JDK 5以及之前的版本中與Parallel Scavenge新生代收集器搭配使用
    • 作為CMS 收集器發(fā)生失敗時(shí)的后備預(yù)案,在并發(fā)收集發(fā)生Concurrent Mode Failure時(shí)使用。這兩點(diǎn)都將在后面的 內(nèi)容中繼續(xù)講解。Serial Old收集器的工作過程如圖所示。

      需要說明一下,Parallel Scavenge新生代收集器架構(gòu)中本身有PS MarkSweep收集器來進(jìn)行老年代收集,并非 直接調(diào)用Serial Old收集器,但是這個(gè)PS MarkSweep收集器與Serial Old的實(shí)現(xiàn)幾乎是一樣的,所以在官 方的許多資料中都是直接以Serial Old代替PS MarkSweep進(jìn)行講解,這里筆者也采用這種方式。

    2.5、Parallel Old收集器

    Parallel Old是Parallel Scavenge收集器的老年代版本,支持多線程并發(fā)收集,基于標(biāo)記-整理算法實(shí) 現(xiàn)。這個(gè)收集器是直到JDK 6時(shí)才開始提供的。

    在此之前,新生代的Parallel Scavenge收集器一直處于相 當(dāng)尷尬的狀態(tài),原因是如果新生代選擇了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器以外別無選擇,其他表現(xiàn)良好的老年代收集器,如CMS無法與它配合工作。由于 老年代Serial Old收集器在服務(wù)端應(yīng)用性能上的“拖累”,使用Parallel Scavenge收集器也未必能在整體上 獲得吞吐量最大化的效果。

    同樣,由于單線程的老年代收集中無法充分利用服務(wù)器多處理器的并行處 理能力,在老年代內(nèi)存空間很大而且硬件規(guī)格比較高級的運(yùn)行環(huán)境中,Parallel Scavenge新生代+老年代Serial Old這種組合的總吞吐量甚至不一 定比ParNew加CMS的組合來得優(yōu)秀。

    直到Parallel Old收集器出現(xiàn)后,“吞吐量優(yōu)先”收集器終于有了比較名副其實(shí)的搭配組合,在注重 吞吐量或者處理器資源較為稀缺的場合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器這個(gè)組 合。Parallel Old收集器的工作過程如圖所示。

    2.6、CMS收集器

    CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。目前很 大一部分的Java應(yīng)用集中在互聯(lián)網(wǎng)網(wǎng)站或者基于瀏覽器的B/S系統(tǒng)的服務(wù)端上,這類應(yīng)用通常都會較為 關(guān)注服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間盡可能短,以給用戶帶來良好的交互體驗(yàn)。CMS收集器就非 常符合這類應(yīng)用的需求。

    從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于標(biāo)記-清除算法實(shí)現(xiàn)的,它的運(yùn)作 過程相對于前面幾種收集器來說要更復(fù)雜一些,整個(gè)過程分為四個(gè)步驟,包括:

  • 初始標(biāo)記(CMS initial mark) :初始標(biāo)記僅僅標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象,速度很快。
  • 并發(fā)標(biāo)記(CMS concurrent mark) :并發(fā)標(biāo)記階段就是從GC Roots的直接關(guān)聯(lián)對象開始遍歷整個(gè)對 象圖的過程,這個(gè)過程耗時(shí)較長但是不需要停頓用戶線程,可以與垃圾收集線程一起并發(fā)運(yùn)行;
  • 重新標(biāo)記(CMS remark) :重 新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的 標(biāo)記記錄,這個(gè)階段的停頓時(shí)間通常會比初始標(biāo)記階段稍長一 些,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短;
  • 并發(fā)清除(CMS concurrent sweep):并發(fā)清除階段,清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的 對象,由于不需要移動存活對象,所以這個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的。
  • 其中初始標(biāo)記、重新標(biāo)記這兩個(gè)步驟仍然需要“Stop The World”。`;

    通過下圖可以比較清楚地看到CMS收集器的運(yùn)作步驟中并發(fā)和需要停頓的階段。


    CMS是一款優(yōu)秀的收集器,它最主要的優(yōu)點(diǎn)在名字上已經(jīng)體現(xiàn)出來:并發(fā)收集、低停頓,一些官 方公開文檔里面也稱之為“并發(fā)低停頓收集器”(Concurrent Low Pause Collector)。CMS收集器是 HotSpot虛擬機(jī)追求低停頓的第一次成功嘗試,但是它還遠(yuǎn)達(dá)不到完美的程度,至少有以下三個(gè)明顯的 缺點(diǎn):

    缺點(diǎn)一:CMS收集器,雖然不會導(dǎo)致用戶線程停頓,但卻會因?yàn)檎加昧艘徊糠志€程(或者說處理器的計(jì) 算能力)而導(dǎo)致應(yīng)用程序變慢,降低總吞吐量。CMS默認(rèn)啟動的回收線程數(shù)是(處理器核心數(shù)量 +3)/4,也就是只占用不超過25%的 處理器運(yùn)算資源。處理器核心數(shù)量不足四個(gè)時(shí), CMS對用戶程序的影響就可能變得很大。就可能導(dǎo)致用戶程序的執(zhí)行速度忽然大幅降低。

    為了緩解這種情況,虛擬機(jī)提 供了一種稱為“增量式并發(fā)收集器”(Incremental Concurrent Mark Sweep/i-CMS)的CMS收集器變種,他是在并發(fā)標(biāo)記、清理的時(shí)候讓收集器線程、用戶線程交替運(yùn)行,盡量減少垃圾收集線程的獨(dú)占資源的 時(shí)間,這樣整個(gè)垃圾收集的過程會更長,但對用戶程序的影響就會顯得較少一些。這就好比長痛不如短痛,交替執(zhí)行,用戶訪問會長時(shí)間一直卡頓,盡可能不會說直接卡死,但是資源有限,訪問會很慢。從 JDK 7開始,i-CMS模式已經(jīng)被聲明不再提倡用戶使用。

    缺點(diǎn)二:CMS收集器無法處理“浮動垃圾”(Floating Garbage),有可能出現(xiàn)“Con-current Mode Failure”失敗進(jìn)而導(dǎo)致另一次完全“Stop The World”的Full GC的產(chǎn)生。

    什么是浮動垃圾?

    在CMS的并發(fā)標(biāo)記和并發(fā)清理階 段,用戶線程是還在繼續(xù)運(yùn)行的,程序在運(yùn)行自然就還會伴隨有新的垃圾對象不斷產(chǎn)生,但這一部分 垃圾對象是出現(xiàn)在標(biāo)記過程結(jié)束以后,CMS無法在當(dāng)次收集中處理掉它們,只好留待下一次垃圾收集 時(shí)再清理掉。這一部分垃圾就稱為“浮動垃圾。

    浮動垃圾帶來的危害?

    同樣也是由于在垃圾收集階段用戶線程還需要持續(xù)運(yùn) 行,那就還需要預(yù)留足夠內(nèi)存空間提供給用戶線程使用,因此CMS收集器不能像其他收集器那樣等待 到老年代幾乎完全被填滿了再進(jìn)行收集。

    在JDK 5的默認(rèn)設(shè)置下,CMS收集器當(dāng)老年代使用了68%的空間后就會被激活,這是一個(gè)偏保守的設(shè)置,如果 在實(shí)際應(yīng)用中老年代增長并不是太快,可以適當(dāng)調(diào)高參數(shù)-XX:CMSInitiatingOccu-pancyFraction的值 來提高CMS的觸發(fā)百分比,降低內(nèi)存回收頻率,獲取更好的性能。

    到了JDK 6時(shí),CMS收集器的啟動 閾值就已經(jīng)默認(rèn)提升至92%。但這又會更容易面臨另一種風(fēng)險(xiǎn):要是CMS運(yùn)行期間預(yù)留的內(nèi)存無法滿 足程序分配新對象的需要,就會出現(xiàn)一次“并發(fā)失敗”(Concurrent Mode Failure),這時(shí)候虛擬機(jī)將不 得不啟動后備預(yù)案:凍結(jié)用戶線程的執(zhí)行,臨時(shí)啟用Serial Old收集器來重新進(jìn)行老年代的垃圾收集, 但這樣停頓時(shí)間就很長了。所以參數(shù)-XX:CMSInitiatingOccupancyFraction設(shè)置得太高將會很容易導(dǎo)致 大量的并發(fā)失敗產(chǎn)生,性能反而降低,用戶應(yīng)在生產(chǎn)環(huán)境中根據(jù)實(shí)際應(yīng)用情況來權(quán)衡設(shè)置。

    缺點(diǎn)三:CMS是一款基于“標(biāo)記-清除”算法實(shí)現(xiàn)的收集器,這意味著收集結(jié)束時(shí)會有大量空間碎片產(chǎn)生??臻g 碎片過多時(shí),將會給大對象分配帶來很大麻煩,往往會出現(xiàn)老年代還有很多剩余空間,但就是無法找 到足夠大的連續(xù)空間來分配當(dāng)前對象,而不得不提前觸發(fā)一次Full GC的情況。

    為了解決這個(gè)問題, CMS收集器提供了一個(gè)-XX:+UseCMS-CompactAtFullCollection開關(guān)參數(shù)(默認(rèn)是開啟的,此參數(shù)從 JDK 9開始廢棄),用于在CMS收集器不得不進(jìn)行Full GC時(shí)開啟內(nèi)存碎片的合并整理過程,由于這個(gè) 內(nèi)存整理必須移動存活對象,(在Shenandoah和ZGC出現(xiàn)前)是無法并發(fā)的。這樣空間碎片問題是解 決了,但停頓時(shí)間又會變長.

    因此虛擬機(jī)設(shè)計(jì)者們還提供了另外一個(gè)參數(shù)-XX:CMSFullGCsBefore- Compaction(此參數(shù)從JDK 9開始廢棄),這個(gè)參數(shù)的作用是要求CMS收集器在執(zhí)行過若干次(數(shù)量 由參數(shù)值決定)不整理空間的Full GC之后,下一次進(jìn)入Full GC前會先進(jìn)行碎片整理(默認(rèn)值為0,表 示每次進(jìn)入Full GC時(shí)都進(jìn)行碎片整理)。

    2.7、Garbage First收集器

    Garbage First(簡稱G1)收集器被Oracle官方稱為“全功能的垃圾收集器”。

    JDK 9發(fā)布之 日,G1宣告取代Parallel Scavenge加Parallel Old組合,成為服務(wù)端模式下的默認(rèn)垃圾收集器,而CMS則 淪落至被聲明為不推薦使用(Deprecate)的收集器。

    在G1收集器出現(xiàn)之前的所有 其他收集器,包括CMS在內(nèi),垃圾收集的目標(biāo)范圍要么是整個(gè)新生代(Minor GC),要么就是整個(gè)老 年代(Major GC),再要么就是整個(gè)Java堆(Full GC)。

    G1不再堅(jiān)持固定大小以及固定數(shù)量的 分代區(qū)域劃分,而是把連續(xù)的Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),Region中還有一類特殊的Humongous區(qū)域,專門用來存儲大對象。每個(gè)Region的大小可以通過參數(shù)-XX:G1HeapRegionSize設(shè) 定。對于那些超過了整個(gè)Region容量的超級大對象, 將會被存放在N個(gè)連續(xù)的Humongous Region之中,G1的大多數(shù)行為都把Humongous Region作為老年代 的一部分來進(jìn)行看待,如下圖所示。

    用戶設(shè)定允許的收集停頓時(shí)間(使用參數(shù)-XX:MaxGCPauseMillis指定,默 認(rèn)值是200毫秒)

    既然有這個(gè)參數(shù)那說明他有控制停頓時(shí)間的功能,他是怎么控制的?

    因?yàn)樗鼘egion作 為單次回收的最小單元,G1收集器去跟蹤各個(gè)Region里面的垃 圾堆積的“價(jià)值”大小 (價(jià)值即回收所獲得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值),然后在后臺維護(hù)一 個(gè)優(yōu)先級列表,根據(jù)用戶設(shè)置的時(shí)間,優(yōu)先處理回收價(jià)值收益最大的那些Region,這也就是“Garbage First”名字的由來。
    這里可以這么理解:他把內(nèi)存分成了多個(gè)小塊,每個(gè)塊占用的內(nèi)存給記錄起來了,根據(jù)用戶設(shè)置的可停頓大小來決定回收塊的大小。


    將Java堆分成多個(gè)獨(dú)立Region后,Region里面存在的跨Region引用對象如何解決?

    使用記憶集避免全堆作為GC Roots掃描,每個(gè)Region都維護(hù)有自己的記憶集,使用到了雙向卡表(卡表是“我指向誰”,這種結(jié)構(gòu)還記錄了“誰指向我”)。Region數(shù)量比傳統(tǒng)收集器的分代數(shù)量明顯要多得多,故此G1至少要耗費(fèi)大約相當(dāng)于Java堆容量10%至20%的額 外內(nèi)存來維持收集器工作。

    在并發(fā)標(biāo)記階段如何保證收集線程與用戶線程互不干擾地運(yùn)行?

    這里首先要解決的是用戶線程改變對象引用關(guān)系時(shí),必須保證其不能打破原本的對象圖結(jié)構(gòu),導(dǎo)致標(biāo)記結(jié)果出現(xiàn)錯(cuò)誤,CMS收集器采用增量更新算法實(shí)現(xiàn),而G1 收集器則是通過原始快照(SATB)算法來實(shí)現(xiàn)的。G1把Region中的一部分空間劃分出來用于并發(fā)回收過 程中的新對象分配,這部分空間不納入回收范圍。與CMS中類似,如果內(nèi)存回收的速度趕不上內(nèi)存分配的速度, G1收集器也要被迫凍結(jié)用戶線程執(zhí)行,導(dǎo)致Full GC而產(chǎn)生長時(shí)間“Stop The World”。

    如果我們不去計(jì)算用戶線程運(yùn)行過程中的動作(如使用寫屏障維護(hù)記憶集的操作),G1收集器的 運(yùn)作過程大致可劃分為以下四個(gè)步驟:

    • 初始標(biāo)記(Initial Marking):僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象,并且修改TAMS 指針的值,讓下一階段用戶線程并發(fā)運(yùn)行時(shí),能正確地在可用的Region中分配新對象。這個(gè)階段需要 停頓線程,但耗時(shí)很短,而且是借用進(jìn)行Minor GC的時(shí)候同步完成的,所以G1收集器在這個(gè)階段實(shí)際 并沒有額外的停頓。

    • 并發(fā)標(biāo)記(Concurrent Marking):從GC Root開始對堆中對象進(jìn)行可達(dá)性分析,遞歸掃描整個(gè)堆 里的對象圖,找出要回收的對象,這階段耗時(shí)較長,但可與用戶程序并發(fā)執(zhí)行。當(dāng)對象圖掃描完成以 后,還要重新處理SATB記錄下的在并發(fā)時(shí)有引用變動的對象。

    • 最終標(biāo)記(Final Marking):對用戶線程做另一個(gè)短暫的暫停,用于處理并發(fā)階段結(jié)束后仍遺留 下來的最后那少量的SATB記錄。

    • 篩選回收(Live Data Counting and Evacuation):負(fù)責(zé)更新Region的統(tǒng)計(jì)數(shù)據(jù),對各個(gè)Region的回 收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的停頓時(shí)間來制定回收計(jì)劃,可以自由選擇任意多個(gè)Region 構(gòu)成回收集,然后把決定回收的那一部分Region的存活對象復(fù)制到空的Region中,再清理掉整個(gè)舊 Region的全部空間。這里的操作涉及存活對象的移動,是必須暫停用戶線程,由多條收集器線程并行 完成的。

    從上述階段的描述可以看出,G1收集器除了并發(fā)標(biāo)記外,其余階段也是要完全暫停用戶線程的, 換言之,它并非純粹地追求低延遲,官方給它設(shè)定的目標(biāo)是在延遲可控的情況下獲得盡可能高的吞吐 量,所以才能擔(dān)當(dāng)起“全功能收集器”的重任與期望。

    毫無疑問,可以由用戶指定期望的停頓時(shí)間是G1收集器很強(qiáng)大的一個(gè)功能,設(shè)置不同的期望停頓 時(shí)間,可使得G1在不同應(yīng)用場景中取得關(guān)注吞吐量和關(guān)注延遲之間的最佳平衡。它默認(rèn)的停頓目標(biāo)為兩百毫秒,如果我們把停頓時(shí)間調(diào)得非常低,譬如設(shè)置為二十毫秒,很可能出現(xiàn)的結(jié) 果就是由于停頓目標(biāo)時(shí)間太短,導(dǎo)致每次選出來的回收集只占堆內(nèi)存很小的一部分,收集器收集的速 度逐漸跟不上分配器分配的速度,導(dǎo)致垃圾慢慢堆積。所以通常 把期望停頓時(shí)間設(shè)置為一兩百毫秒或者兩三百毫秒會是比較合理的。

    從G1開始,不追求一次把整個(gè)Java堆全部清理干凈。只要收集的速度能跟得上對象分配的速度,那一切就能運(yùn)作得很完美。

    G1和CMS區(qū)別:

  • G1是分Region的內(nèi)存布局、CMS采用分代
  • CMS“標(biāo)記-清除”算法,G1基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器
  • G1每個(gè)Region都有一個(gè)記憶集,存儲對象的引用情況,主要是防止跨區(qū)引用。Region比較多,內(nèi)存占用自然大。CMS的記憶集只有一個(gè),只需要處理老年代到新生代的引用。
  • 目前在小內(nèi)存應(yīng)用上CMS的表現(xiàn)大概率仍然要會優(yōu)于G1,而在大內(nèi)存應(yīng)用上G1則大多能發(fā)揮其 優(yōu)勢,這個(gè)優(yōu)劣勢的Java堆容量平衡點(diǎn)通常在6GB至8GB之間,當(dāng)然,以上這些也僅是經(jīng)驗(yàn)之談,不 同應(yīng)用需要量體裁衣地實(shí)際測試才能得出最合適的結(jié)論,隨著HotSpot的開發(fā)者對G1的不斷優(yōu)化,也 會讓對比結(jié)果繼續(xù)向G1傾斜。

    三、總結(jié)

    Java垃圾收集器也是在不斷的進(jìn)化當(dāng)中,從第一版Serial單線程收集器,再到ParNew收集器,再到吞吐量優(yōu)先Parallel Scavenge收集器,再到以獲取最短回收停頓時(shí)間為目標(biāo)的CMS收集器,再到G1全功能的垃圾收集器。其實(shí)很多情況下,我們對收集器是無感知的。甚至很少一部分才會去真正了解收集器,甚至只知道垃圾回收是JVM做的,其他不關(guān)我的事。

    我個(gè)人感覺收集器就好比手機(jī),雖然我們?nèi)巳嗽谟?#xff0c;但是根本沒人關(guān)心他到底是怎么做到的。不說別的,在了解完收集器之后,最起碼能讓你對JVM的了解更上一個(gè)檔次,在面試的時(shí)候也會讓自己游刃有余。

    3.1、JVM默認(rèn)用的哪個(gè)收集器?

    查看當(dāng)前JVM的垃圾收集器
    cmd中輸入以下命令:

    java -XX:+PrintCommandLineFlags -version

    JDK1.7 默認(rèn)垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
    JDK1.8 默認(rèn)垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
    JDK1.9 默認(rèn)垃圾收集器G1

    在jdk8可以切換收集器嗎?

    可以,通過-XX:+UseG1GC便可以切換G1收集器。但是一般情況下不要切換,JDK8默認(rèn)用了Parallel收集器,那他一定有他的原因,不管是版本兼容還是什么問題,總之肯定有他的原因,所以一般情況下不建議自己更改收集器,采用默認(rèn)的即可。真正需要優(yōu)化了,可以選擇更改Parallel收集器的參數(shù),例如新生代老年代大小等等。

    3.2、Serial收集器

    特點(diǎn)

  • JDK 1.3.1之前是HotSpot虛擬機(jī)新生代 收集器的唯一選擇
  • 采用了整理復(fù)制算法
  • 單線程工作的收集器
  • 優(yōu)點(diǎn)

    • 占用內(nèi)存少,適合用在客戶端,客戶端一般是單線程并且占用內(nèi)存少,而Serial收集器完全可以滿足。

    缺點(diǎn)

    • 進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程,直到它收集結(jié)束

    3.3、ParNew收集器

    特點(diǎn)

  • 在JDK7首選的新生代收集器
  • ParNew收集器實(shí)質(zhì)上是Serial收集器的多線程并行版本
  • 采用了整理復(fù)制算法
  • 能與CMS 收集器配合工作
  • 優(yōu)點(diǎn)

    • 能與CMS 收集器配合工作

    缺點(diǎn)

  • 進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程,直到它收集結(jié)束
  • ParNew收集器在單核處理器的環(huán)境中絕對不會比Serial收集器有更好的效果,反而會有線程切換開銷。
  • 3.4、Parallel Scavenge收集器

    特點(diǎn)

  • 新生代收集器
  • 基于標(biāo)記-復(fù)制算法
  • 目標(biāo)是達(dá)到一個(gè)可控制的吞吐 量
  • 優(yōu)點(diǎn)

    • 提供了兩個(gè)參數(shù)可以控制停頓時(shí)間還有吞吐量大小,相比用其他的收集器,假如不懂收集器機(jī)制調(diào)優(yōu)很困難,而Parallel Scavenge收集器,我們只需要設(shè)置這兩個(gè)參數(shù)再加上堆大小,其他的交給虛擬機(jī)來控制即可。

    缺點(diǎn)

    • 控制停頓時(shí)間,就是將新生代調(diào)小,反而會導(dǎo)致頻繁GC

    可控制參數(shù)

    • -XX:MaxGCPauseMillis :控制最大垃圾收集停頓時(shí)間
    • -XX:GCTimeRatio :設(shè)置吞吐量大小
    • -XX:+UseAdaptiveSizePolicy :這是一 個(gè)開關(guān)參數(shù),當(dāng)這個(gè)參數(shù)被激活之后,就不需要人工指定新生代的大小(-Xmn)、Eden與Survivor區(qū) 的比例(-XX:SurvivorRatio)、晉升老年代對象大小(-XX:PretenureSizeThreshold)等細(xì)節(jié)參數(shù) 了,虛擬機(jī)會根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí) 間或者最大的吞吐量。

    3.5、Serial Old收集器

  • Serial Old是Serial收集器的老年代版本
  • 標(biāo)記-整理
  • 3.6、Parallel Old收集器

  • Parallel Old是Parallel Scavenge收集器的老年代版本
  • 標(biāo)記-整理
  • Parallel Scavenge新生代收集器,當(dāng)時(shí)老年代除了Serial Old(PS MarkSweep)收集器以外別無選擇,直到Parallel Old收集器出世,。
  • 3.7、CMS收集器

    特點(diǎn)

  • 以獲取最短回收停頓時(shí)間為目標(biāo)
  • 標(biāo)記-清除
  • 老年代收集器
  • 優(yōu)點(diǎn):通過分步驟來降低停頓時(shí)間

  • 初始標(biāo)記(CMS initial mark) :初始標(biāo)記僅僅標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象,速度很快。
  • 并發(fā)標(biāo)記(CMS concurrent mark) :并發(fā)標(biāo)記階段就是從GC Roots的直接關(guān)聯(lián)對象開始遍歷整個(gè)對 象圖的過程。
  • 重新標(biāo)記(CMS remark) :重 新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的 標(biāo)記記錄。
  • 并發(fā)清除(CMS concurrent sweep):并發(fā)清除階段,清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的 對象。
  • 其中初始標(biāo)記、重新標(biāo)記這兩個(gè)步驟仍然需要“Stop The World”。
  • 缺點(diǎn)

  • CMS收集器,雖然將垃圾回收分成了幾步,間接的來區(qū)分出來哪些需要停止用戶線程,雖然這樣做可以在某程度上不會導(dǎo)致用戶線程停頓,卻會占用了一部分線程(或者說處理器的計(jì) 算能力),而導(dǎo)致應(yīng)用程序變慢,降低總吞吐量。
  • 邊清理邊運(yùn)行的垃圾,垃圾回收沒辦法及時(shí)清理,因此還需要空出來很大的內(nèi)存來備用存放浮動垃圾。浮動垃圾的多少在于項(xiàng)目并發(fā)情況,假如那一瞬間訪問特別多,那自然占用堆內(nèi)存會很大,浮動垃圾空留空間是可以通過參數(shù)調(diào)的。
  • 基于標(biāo)記清除,就會有內(nèi)存碎片問題。
  • 可控制參數(shù)

    • -XX:+UseCMS-CompactAtFullCollection:開啟標(biāo)記整理,整理階段是沒辦法運(yùn)行用戶線程的。
    • -XX:CMSFullGCsBefore- Compaction:該參數(shù)可以設(shè)置次數(shù),也就是gc幾次的時(shí)候,用一次標(biāo)記整理,避免了非得到無法分配的情況才去整理,導(dǎo)致線程停頓。

    3.8、G1收集器

    特點(diǎn)

  • JDK 9 G1宣告取代Parallel Scavenge加Parallel Old組合
  • 目標(biāo)是在延遲可控的情況下獲得盡可能高的吞吐量
  • 不再采用年輕代、老年代,而是把連續(xù)的Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),那也就是說別的收集器都采用了分代,老年代和新生代各自有各自的收集器,所以必須要組合起來用,而G1被Oracle官方稱為“全功能的垃圾收集器”,也就是只需要用這一個(gè)收集器。
  • 優(yōu)點(diǎn)

  • G1收集器去跟蹤各個(gè)Region里面的垃 圾堆積的“價(jià)值”大小,維護(hù)一 個(gè)優(yōu)先級列表,根據(jù)用戶設(shè)置的時(shí)間,優(yōu)先處理回收價(jià)值收益最大的那些Region。說白了,就是你能接受的停頓時(shí)間大,收集器就能多釋放點(diǎn)內(nèi)存,假如你設(shè)置的小,他就只能少釋放點(diǎn),盡可能減少停頓時(shí)間,但是不可能控制的那么精準(zhǔn)。

  • 他跟CMS一樣,采用了分步來完成收集:

  • 初始標(biāo)記(Initial Marking):僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象,停頓線程,但耗時(shí)很短,而且是借用進(jìn)行Minor GC的時(shí)候同步完成的
  • 并發(fā)標(biāo)記(Concurrent Marking):從GC Root開始對堆中對象進(jìn)行可達(dá)性分析,找出要回收的對象
  • 最終標(biāo)記(Final Marking):用于處理并發(fā)階段結(jié)束后仍遺留 下來的最后那少量的SATB記錄(SATB記錄我理解的就是回收和用戶線程并行時(shí)候所產(chǎn)生的記錄)。
  • 篩選回收(Live Data Counting and Evacuation):負(fù)責(zé)更新Region的統(tǒng)計(jì)數(shù)據(jù),對各個(gè)Region的回 收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的停頓時(shí)間來制定回收計(jì)劃
  • 除了并發(fā)標(biāo)記外,其余階段也是要完全暫停用戶線程的
  • 缺點(diǎn)

  • 使用記憶集來記錄夸區(qū)域引用對象,使用記憶集避免全堆作為GC Roots掃描,每個(gè)Region都維護(hù)有自己的記憶集,Region數(shù)量多,故此G1至少要耗費(fèi)大約相當(dāng)于Java堆容量10%至20%的額 外內(nèi)存來維持收集器工作。
  • 可控制參數(shù)

    • -XX:G1HeapRegionSize:設(shè)置每個(gè)Region的大小
    • -XX:MaxGCPauseMillis:設(shè)定允許的收集停頓時(shí)間

    總結(jié)

    以上是生活随笔為你收集整理的(七)Java垃圾收集器详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。