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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

JVM面试(四)-垃圾回收、垃圾收集器、GC日志

發(fā)布時(shí)間:2024/1/8 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM面试(四)-垃圾回收、垃圾收集器、GC日志 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

垃圾回收、垃圾收集器、GC日志

  • 什么是垃圾?(垃圾的概念)
  • 什么是垃圾回收?(垃圾回收的概念)
  • 為什么要垃圾回收?(垃圾回收的原因)
  • 如何定義垃圾?
    • 引用計(jì)數(shù)算法
      • 什么是循環(huán)引用
    • 可達(dá)性分析算法
      • 哪些可以作為GC Roots?
      • GC Roots的判斷辦法(如何判斷是不是GC Roots)
      • 如何查看GC Roots
      • 關(guān)于finalize方法
    • 閱讀參考
    • 小結(jié)
  • 強(qiáng)引用、軟引用、弱引用、虛引用
  • 分代收集理論
  • Minor GC、Major GC和Full GC
    • 三種GC的區(qū)別
    • 三種GC觸發(fā)條件和解決辦法(重要!!!)
    • 閱讀參考
  • Stop the World-STW
    • System.gc()
  • 內(nèi)存溢出和內(nèi)存泄漏(重要)
    • 閱讀參考
  • 垃圾回收算法
    • 標(biāo)記-清除算法(Mark-Sweep)
    • 標(biāo)記-復(fù)制算法(Copying)
    • 標(biāo)記-整理算法(Mark-Compact)
    • 分代收集算法(Generational Collecting)
    • 分區(qū)收集算法
    • 增量收集算法(Incremental Collecting)
    • 閱讀參考
  • GC性能指標(biāo)
    • 閱讀參考
  • 幾個(gè)概念(下面的垃圾收集器中會(huì)遇到)
    • 并行-Parallel 與 并發(fā)(重要!!!)
    • 根節(jié)點(diǎn) 枚舉(動(dòng)詞)
    • 安全點(diǎn)
    • 安全區(qū)域
    • 閱讀參考
  • 垃圾收集器
    • 垃圾收集器分類
    • Serial垃圾收集器
      • 特點(diǎn)
      • 參數(shù)
      • 優(yōu)勢(shì)
      • 應(yīng)用場(chǎng)景
    • Serial Old(老年代)垃圾收集器
      • 特點(diǎn)
      • 參數(shù)
    • Par(Parallel)New(New表示只能處理新生代)垃圾收集器
      • 特點(diǎn)
      • 參數(shù)
      • 應(yīng)用場(chǎng)景
      • 為什么只有ParNew能與CMS收集器配合?
    • Parallel Scavenge垃圾收集器(吞吐量?jī)?yōu)先收集器,新生代)
      • 特點(diǎn)
      • 參數(shù)
      • 應(yīng)用場(chǎng)景
    • Parallel Old垃圾收集器
      • 特點(diǎn)
      • 參數(shù)
    • CMS(Concurrent Mark Sweep)垃圾收集器(重要!!!)
      • 特點(diǎn)
      • 缺點(diǎn)(重要!!!)
      • 參數(shù)
      • 應(yīng)用場(chǎng)景
      • CMS回收過(guò)程
    • G1(Garbage-First)垃圾收集器(重要!!!)
      • 特點(diǎn)
      • 參數(shù)
      • 優(yōu)勢(shì)
      • 應(yīng)用場(chǎng)景
      • G1回收過(guò)程
    • G1和CMS比較-待完善(重要!!!)
    • 閱讀參考
  • 垃圾收集器總結(jié)
    • 新生代收集器/老年代收集器
    • 吞吐量?jī)?yōu)先、停頓時(shí)間優(yōu)先
    • 串行、并行、并發(fā)(重要!!!)
    • 回收算法
    • JDK默認(rèn)垃圾收集器(重要!!!)
    • 表格
    • 垃圾收集器參數(shù)總結(jié)
    • 如何選擇垃圾收集器(重要!!!)
  • 回收方法區(qū)(重要!!!)
    • 方法區(qū)相關(guān)參數(shù)
  • GC日志
    • GC日志相關(guān)參數(shù)
    • GC日志內(nèi)容

什么是垃圾?(垃圾的概念)

沒(méi)有任何指針指向的對(duì)象 就是垃圾

什么是垃圾回收?(垃圾回收的概念)

垃圾回收(Garbage Collection,GC),顧名思義就是 釋放 垃圾占用的空間,防止內(nèi)存泄露
有效的 使用(動(dòng)詞) 可使用的內(nèi)存,對(duì) 內(nèi)存堆中 已經(jīng)死亡的 或者 長(zhǎng)時(shí)間沒(méi)有使用的對(duì)象 進(jìn)行 清除和回收

為什么要垃圾回收?(垃圾回收的原因)

  • 垃圾需要及時(shí)被清理,如果一直不進(jìn)行清理,這些垃圾對(duì)象所占用的空間和會(huì)一直保留到應(yīng)用程序結(jié)束,被保留的空間無(wú)法被其他對(duì)象使用,甚至導(dǎo)致內(nèi)存溢出
  • 垃圾回收 除了 釋放沒(méi)有用的對(duì)象,還可以 清除 內(nèi)存里的記錄碎片,碎片整理將堆內(nèi)存已到堆的一端,以便JVM將整理出的內(nèi)存分配給新的對(duì)象
  • 隨著應(yīng)用程序越來(lái)越復(fù)雜,用戶越來(lái)越多,沒(méi)有GC就不能保證應(yīng)用程序的正常進(jìn)行

如何定義垃圾?

  • 在進(jìn)行垃圾回收之前,需要判斷哪些對(duì)象是存活對(duì)象,哪些是死亡對(duì)象,只有被標(biāo)記為死亡的對(duì)象才能夠被回收
  • 當(dāng)一個(gè)對(duì)象 已經(jīng)不再被 任何的存活對(duì)象 繼續(xù)引用 的時(shí)候,就可以宣判為已經(jīng)死亡
  • 判斷對(duì)象是否存活一般有兩種方式:引用計(jì)數(shù)算法 和 可達(dá)性分析算法

引用計(jì)數(shù)算法

通過(guò) 在 對(duì)象頭中 分配一個(gè)空間 來(lái)保存 該對(duì)象被引用的次數(shù)(Reference Count)。如果該對(duì)象被其它對(duì)象引用,則它的引用計(jì)數(shù)加1,如果刪除對(duì)該對(duì)象的引用,那么它的引用計(jì)數(shù)就減1,當(dāng)該對(duì)象的引用計(jì)數(shù)為0時(shí),那么該對(duì)象就會(huì)被回收

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

  • 實(shí)現(xiàn)簡(jiǎn)單、垃圾便于辨識(shí)
  • 判定效率高,回收沒(méi)有延遲

缺點(diǎn):

  • 需要 單獨(dú)的字段 存儲(chǔ)計(jì)數(shù)器,額外的存儲(chǔ)空間開(kāi)銷
  • 需要 更新 計(jì)數(shù)器,伴隨著加法和減法操作,帶來(lái)時(shí)間開(kāi)銷
  • 無(wú)法處理循環(huán)引用的情況

什么是循環(huán)引用

  • 定義2個(gè)對(duì)象
  • 相互引用
  • 置空各自的聲明引用
  • 此時(shí)由于他們相互引用著對(duì)方,導(dǎo)致它們的引用計(jì)數(shù)永遠(yuǎn)都不會(huì)為0,通過(guò)引用計(jì)數(shù)算法,很難解決對(duì)象之間循環(huán)引用問(wèn)題,也就永遠(yuǎn)無(wú)法通知GC收集器回收它們

    可達(dá)性分析算法

    通過(guò)一些 被稱為 引用鏈(GC Roots)的對(duì)象 作為起點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索走過(guò)的路徑被稱為(Reference Chain),當(dāng) 一個(gè)對(duì)象 到 GC Roots 沒(méi)有任何引用鏈相連時(shí)(即從 GC Roots 節(jié)點(diǎn)到該節(jié)點(diǎn)不可達(dá)),則證明該對(duì)象是不可用的

    優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,執(zhí)行高效,而且能夠解決循環(huán)引用的問(wèn)題

    要使用可達(dá)性分析算法判斷內(nèi)存是否可回收,那么 分析工作 必須在 一個(gè)能保障一致性 的 快照中進(jìn)行,為了保持一致性,GC的時(shí)候必須Stop The World(STW)

    哪些可以作為GC Roots?

    • 虛擬機(jī)棧(棧幀中的本地變量表)中 引用的對(duì)象
    • 方法區(qū)中 類(的)靜態(tài)屬性(static變量) 引用的對(duì)象
    • 方法區(qū)中 常量(比如字符串常量池) 引用的對(duì)象
    • 本地方法棧中 JNI(即一般說(shuō)的 Native 方法)引用的對(duì)象
    • 所有 被同步鎖synchronized 持有的對(duì)象
    • Java內(nèi)部的引用(基本數(shù)據(jù)類型 對(duì)應(yīng)的 Class對(duì)象、一些常駐的異常對(duì)象[NullPointException、OutOfMemroyError等]、系統(tǒng)類加載器)
    • 反應(yīng)Java虛擬機(jī)內(nèi)部情況的JMXBean、JVMTI注冊(cè)的回調(diào)、本地代碼緩存等

    GC Roots的判斷辦法(如何判斷是不是GC Roots)

    如果一個(gè)指針,指向了 堆內(nèi)存里面的對(duì)象,但是 自己又不存放在堆里面(虛擬機(jī)棧、本地方法棧中 指向堆區(qū)對(duì)象 的 引用、方法區(qū)中 對(duì) 堆區(qū) 靜態(tài)變量 以及 字符串常量 的 引用),那它就是一個(gè)Root

    如何查看GC Roots

    閱讀參考:使用MAT和JProfiler查看GC Roots

    關(guān)于finalize方法

    在可達(dá)性分析算法中,不可達(dá)的對(duì)象,暫時(shí)處于“緩刑”階段,要真正宣告一個(gè)對(duì)象死亡,至少要經(jīng)歷過(guò)兩次標(biāo)記過(guò)程

    如果 對(duì)象 在可達(dá)性分析后 發(fā)現(xiàn) 沒(méi)有與GC Roots相連接的引用鏈,那它將會(huì)被 第一次標(biāo)記 并且 進(jìn)行一次篩選,篩選的條件是 此對(duì)象 是否有必要執(zhí)行 finalize()方法

    finalize()方法是Object類中定義的,允許在任何子類中被重寫,用于 對(duì)象被回收時(shí) 進(jìn)行資源釋放

    當(dāng)對(duì)象沒(méi)有覆蓋finalize()方法,或者已經(jīng)被調(diào)用過(guò)finalize()了,JVM將這兩種情況都視為“沒(méi)有必須要執(zhí)行” finalize()方法

    如果被判定為“有必要執(zhí)行”finalize()方法,那么該對(duì)象將會(huì)被放置在一個(gè)名為F-Queue的隊(duì)列之中,并在稍后 由一條 由虛擬機(jī)自動(dòng)建立的、低調(diào)度優(yōu)先級(jí) 的 Finalizer線程 去執(zhí)行 它們的finalize()方法

    這里所說(shuō)的“執(zhí)行” 是指 虛擬機(jī) 會(huì)觸發(fā)這個(gè)方法開(kāi)始運(yùn)行,但并不承諾一定會(huì)等待它運(yùn)行結(jié)束

    這樣做的原因是,如果某個(gè)對(duì)象的finalize()方法執(zhí)行緩慢,或者更極端地發(fā)生了死循環(huán),將很可能導(dǎo)致F-Queue隊(duì)列中的其他對(duì)象永久處于等待,甚至導(dǎo)致整個(gè)內(nèi)存回收子系統(tǒng)的崩潰

    finalize()方法是對(duì)象逃脫死亡命運(yùn)的最后一次機(jī)會(huì),稍后收集器將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模的標(biāo)記
    如果對(duì)象要在finalize()中成功拯救自己,只要重新與引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián)即可,譬如 把自己(this關(guān)鍵字)賦值給 某個(gè)類變量 或者 對(duì)象的成員變量,那在第二次標(biāo)記時(shí) 它將被移出 “即將回收”的集合
    如果對(duì)象這時(shí)候還沒(méi)有逃脫,那基本上它就真的要被回收了

    重點(diǎn)!!!:任何一個(gè)對(duì)象 的 finalize方法 都只會(huì)被系統(tǒng) 自動(dòng)調(diào)用一次

    閱讀參考

    對(duì)象的finalization機(jī)制、finalize方法理解(認(rèn)真閱讀)

    小結(jié)

    • 基于可達(dá)性分析法的GC垃圾回收的效率較高,實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單(引用計(jì)算法是算法簡(jiǎn)單,實(shí)現(xiàn)較難),但是其缺點(diǎn)在于GC期間,整個(gè)應(yīng)用需要被掛起(STW,Stop-the-world,下面都簡(jiǎn)稱STW),后面很多此類算法的提出,都是在解決這個(gè)問(wèn)題(縮小 STW 時(shí)間)
    • 在可達(dá)性分析類GC中,即使對(duì)象變成了垃圾,程序也無(wú)法立刻感知,直到 GC 執(zhí)行前,始終都會(huì)有一部分內(nèi)存空間被垃圾占用
    • 基于引用計(jì)數(shù)法的GC,天然帶有增量特性(就是GC過(guò)程中可能還會(huì)有新的GC產(chǎn)生),可與應(yīng)用交替運(yùn)行,不需要暫停應(yīng)用;同時(shí),在引用計(jì)數(shù)法中,每個(gè)對(duì)象始終都知道自己的被引用數(shù),當(dāng)計(jì)數(shù)器為0時(shí),對(duì)象可以馬上回收
    • 上述兩類GC各有千秋,總體來(lái)說(shuō),基于可達(dá)性分析的GC還是占據(jù)了主流,究其原因,首先,引用計(jì)數(shù)算法無(wú)法解決「循環(huán)引用無(wú)法回收」的問(wèn)題,即兩個(gè)對(duì)象互相引用,所以各對(duì)象的計(jì)數(shù)器的值都是 1,即使這些對(duì)象都成了垃圾(無(wú)外部引用),GC也無(wú)法將它們回收。當(dāng)然上面這一點(diǎn)還不是引用計(jì)數(shù)法最大的弊端,引用計(jì)數(shù)算法最大的問(wèn)題在于:計(jì)數(shù)器值的增減處理非常繁重,譬如對(duì)根對(duì)象的引用,此外,多個(gè)線程之間 共享對(duì)象時(shí) 需要對(duì)計(jì)數(shù)器 進(jìn)行原子 遞增/遞減,這本身又帶來(lái)了一系列新的復(fù)雜性和問(wèn)題

    閱讀參考:垃圾標(biāo)記階段-引用計(jì)數(shù)算法、可達(dá)性分析算法

    強(qiáng)引用、軟引用、弱引用、虛引用

    • 強(qiáng)引用,最傳統(tǒng)的“引用”的定義,是指 在程序代碼之中 普遍存在 的 引用賦值,即類似“Object obj=new Object()”這種引用關(guān)系。無(wú)論任何情況下,只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象
    • 軟引用 是用來(lái)描述一些 還有用,但非必須的對(duì)象只被軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常前,會(huì)把這些對(duì)象 列進(jìn) 回收范圍之中 進(jìn)行 第二次回收,如果這次回收還沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。在JDK 1.2版之后提供了SoftReference類來(lái)實(shí)現(xiàn)軟引用
    • 弱引用 也是用來(lái)描述那些 非必須對(duì)象,但是 它的強(qiáng)度 比 軟引用更弱一些,被弱引用關(guān)聯(lián)的對(duì)象 只能生存到 下一次垃圾收集發(fā)生為止當(dāng)垃圾收集器開(kāi)始工作,無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象。在JDK 1.2版之后提供了WeakReference類來(lái)實(shí)現(xiàn)弱引用
    • 虛引用也稱為“幽靈引用”或者“幻影引用”,它是最弱的一種引用關(guān)系。一個(gè)對(duì)象 是否有 虛引用的存在,完全不會(huì)對(duì)其 生存時(shí)間 構(gòu)成影響,也無(wú)法通過(guò) 虛引用 來(lái)取得(get) 一個(gè)對(duì)象實(shí)例。為一個(gè)對(duì)象 設(shè)置 虛引用關(guān)聯(lián) 的 唯一目的 只是為了 能在這個(gè)對(duì)象 被收集器回收時(shí) 收到一 個(gè)系統(tǒng)通知。在JDK 1.2版之后提供了PhantomReference類來(lái)實(shí)現(xiàn)虛引用

    分代收集理論

    當(dāng)前商業(yè)虛擬機(jī)的垃圾收集器,大多數(shù)都遵循了“分代收集”
    分代收集名為理論,實(shí)質(zhì)是一套符合大多數(shù)程序運(yùn)行實(shí)際情況的經(jīng)驗(yàn)法則,它建立在兩個(gè)分代假說(shuō)之上:

    • 弱分代假說(shuō)(Weak Generational Hypothesis):絕大多數(shù)對(duì)象都是朝生夕滅的
    • 強(qiáng)分代假說(shuō)(Strong Generational Hypothesis):熬過(guò)越多次垃圾收集過(guò)程的對(duì)象就越難以消亡

    這兩個(gè)分代假說(shuō)共同奠定了多款常用的垃圾收集器的一致的設(shè)計(jì)原則:收集器應(yīng)該將Java堆劃分出不同的區(qū)域,然后將回收對(duì)象依據(jù)其年齡(年齡即對(duì)象熬過(guò)垃圾收集過(guò)程的次數(shù))分配到不同的區(qū)域之中存儲(chǔ)

    顯而易見(jiàn),如果一個(gè)區(qū)域中大多數(shù)對(duì)象都是朝生夕滅,難以熬過(guò)垃圾收集過(guò)程的話,那么把它們集中放在一起,每次回收時(shí)只關(guān)注如何保留少量存活而不是去標(biāo)記那些大量將要被回收的對(duì)象,就能以較低代價(jià)回收到大量的空間

    如果剩下的都是難以消亡的對(duì)象,那把它們集中放在一塊,虛擬機(jī)便可以使用較低的頻率來(lái)回收這個(gè)區(qū)域,這就同時(shí)兼顧了垃圾收集的時(shí)間開(kāi)銷和內(nèi)存的空間有效利用

    在Java堆劃分出不同的區(qū)域之后,垃圾收集器才可以每次只回收其中某一個(gè)或者某些部分的區(qū)域——因而才有了“Minor GC”、“Major GC”、“Full GC”這樣的回收類型的劃分
    也才能夠 針對(duì)不同的區(qū)域 安排 與里面存儲(chǔ)對(duì)象存亡特征 相匹配 的 垃圾收集算法
    因而發(fā)展出了“標(biāo)記-復(fù)制算法”“標(biāo)記-清除算法”“標(biāo)記-整理算法”等針對(duì)性的垃圾收集算法

    把分代收集理論具體放到現(xiàn)在的商用Java虛擬機(jī)里,設(shè)計(jì)者一般至少會(huì)把Java堆劃分為新生代(Young Generation)和老年代(Old Generation)兩個(gè)區(qū)域
    在新生代中,每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對(duì)象死去,而每次回收后存活的少量對(duì)象,將會(huì)逐步晉升到老年代中存放

    分代收集并非只是簡(jiǎn)單劃分一下內(nèi)存區(qū)域那么容易,它至少存在一個(gè)明顯的困難:對(duì)象不是孤立的,對(duì)象之間會(huì)存在跨代引用

    假如要現(xiàn)在進(jìn)行一次只局限于新生代區(qū)域內(nèi)的收集(Minor GC),但新生代中的對(duì)象是完全有可能被老年代所引用的,為了找出該區(qū)域中的存活對(duì)象,不得不在固定的GC Roots之外,再額外遍歷整個(gè)老年代中所有對(duì)象來(lái)確保可達(dá)性分析結(jié)果的正確性,反過(guò)來(lái)也是一樣

    遍歷整個(gè)老年代所有對(duì)象的方案雖然理論上可行,但無(wú)疑會(huì)為內(nèi)存回收帶來(lái)很大的性能負(fù)擔(dān)
    為了解決這個(gè)問(wèn)題,就需要對(duì)分代收集理論添加第三條經(jīng)驗(yàn)法則:

    跨代引用假說(shuō)(Intergenerational Reference Hypothesis):跨代引用 相對(duì)于 同代引用來(lái)說(shuō)僅占極少數(shù)

    這其實(shí)是可根據(jù)前兩條假說(shuō)邏輯推理得出的隱含推論:存在互相引用關(guān)系的兩個(gè)對(duì)象,是應(yīng)該傾向于同時(shí)生存或者同時(shí)消亡的
    舉個(gè)例子,如果某個(gè)新生代對(duì)象存在跨代引用,由于老年代對(duì)象難以消亡,該引用會(huì)使得新生代對(duì)象在收集時(shí)同樣得以存活,進(jìn)而在年齡增長(zhǎng)之后晉升到老年代中,這時(shí)跨代引用也隨即被消除了

    依據(jù)這條假說(shuō),就不應(yīng)再為了 少量的跨代引用 去掃描 整個(gè)老年代,也不必 浪費(fèi)空間 專門記錄 每一個(gè)對(duì)象是否存在 及 存在哪些跨代引用,只需 在新生代上 建立一個(gè) 全局的數(shù)據(jù)結(jié)構(gòu)(該結(jié)構(gòu)被稱為“記憶集”,Remembered Set),這個(gè)結(jié)構(gòu) 把老年代 劃分成 若干小塊,標(biāo)識(shí)出 老年代的哪一塊內(nèi)存 會(huì)存在 跨代引用

    此后當(dāng)發(fā)生Minor GC時(shí),只有 包含了 跨代引用的小塊內(nèi)存里的對(duì)象 才會(huì)被加入到 GC Roots進(jìn)行掃描
    雖然這種方法需要在對(duì)象改變引用關(guān)系(如將自己或者某個(gè)屬性賦值)時(shí)維護(hù)記錄數(shù)據(jù)的正確性,會(huì)增加一些運(yùn)行時(shí)的開(kāi)銷,但比起收集時(shí)掃描整個(gè)老年代來(lái)說(shuō)仍然是劃算的

    Minor GC、Major GC和Full GC

    三種GC的區(qū)別

    • 部分收集(Partial GC):指目標(biāo)不是完整收集整個(gè)Java堆的垃圾收集,其中又分為:
    • 新生代收集(Minor GC/Young GC):指目標(biāo)只是新生代的垃圾收集(包括 Eden 和 Survivor 區(qū)域), Minor GC會(huì)引發(fā)STW(Stop the World),暫停其他用戶線程,等待垃圾回收結(jié)束,用戶線程才會(huì)恢復(fù)運(yùn)行
    • 老年代收集(Major GC/Old GC):指 目標(biāo) 只是老年代 的 垃圾收集,目前只有CMS收集器會(huì)有單獨(dú)收集老年代的行為。出現(xiàn)Major GC,經(jīng)常會(huì)伴隨 至少一次的Minor GC
    • 混合收集(Mixed GC):指 目標(biāo) 是 收集 整個(gè)新生代 以及 部分老年代的垃圾收集,目前只有G1收集器會(huì)有這種行為
    • 整堆收集(Full GC):收集整個(gè)Java堆和方法區(qū)的垃圾收集,包括年輕代和老年代
    • Major GC比Minor GC慢10倍以上,STW時(shí)間更長(zhǎng)

    三種GC觸發(fā)條件和解決辦法(重要!!!)

    • Minor GC:只有Eden區(qū)滿的時(shí)候才會(huì)觸發(fā)Minor GC,s0 或 s1 滿的時(shí)候都不會(huì)觸發(fā)GC
    • Major GC:老年代空間不足時(shí)
    • Full GC:
    • 調(diào)用System.gc()
    • 老年代空間不足,老年代空間不足的常見(jiàn)場(chǎng)景為大對(duì)象直接進(jìn)入老年代、長(zhǎng)期存活的對(duì)象進(jìn)入老年代等。 為了避免以上原因引起的 Full GC,應(yīng)當(dāng)盡量不要?jiǎng)?chuàng)建過(guò)大的對(duì)象以及數(shù)組。除此之外,可以通過(guò) -Xmn 虛擬機(jī)參數(shù)調(diào)大新生代的大小,讓對(duì)象盡量在新生代被回收掉,不進(jìn)入老年代。還可以通過(guò) -XX:MaxTenuringThreshold 調(diào)大對(duì)象進(jìn)入老年代的年齡,讓對(duì)象在新生代多存活一段時(shí)間
    • JDK1.7及以前的方法區(qū)(永久代)空間不足,在JDK 1.7及以前,HotSpot 虛擬機(jī)中的方法區(qū)是用永久代實(shí)現(xiàn)的,永久代中存放的為一些 Class 的信息、常量、靜態(tài)變量等數(shù)據(jù)。 當(dāng)系統(tǒng)中要加載的類、反射的類和調(diào)用的方法較多時(shí),永久代可能會(huì)被占滿,在未配置為采用 CMS的情況下也會(huì)執(zhí)行 Full GC。如果經(jīng)過(guò) Full GC 仍然回收不了,那么虛擬機(jī)會(huì)拋出 java.lang.OutOfMemoryError。 為避免以上原因引起的 Full GC,可采用的方法為 增大永久代空間 或轉(zhuǎn)為 使用CMS
    • 通過(guò)Minor GC后 進(jìn)入老年代的對(duì)象的平均大小 大于 老年代可用內(nèi)存、從Eden區(qū) 直接把對(duì)象 復(fù)制到 老年代的時(shí)候 老年代可用空間 小于 該對(duì)象大小
    • Concurrent Mode Failure,執(zhí)行 CMS GC 的過(guò)程中 同時(shí)有對(duì)象要放入老年代,而此時(shí)老年代空間不足(可能是 GC 過(guò)程中浮動(dòng)垃圾過(guò)多導(dǎo)致暫時(shí)性的空間不足),便會(huì)報(bào) Concurrent Mode Failure 錯(cuò)誤,并觸發(fā) Full GC
    • 空間分配擔(dān)保失敗,使用復(fù)制算法的 Minor GC 需要老年代的內(nèi)存空間作擔(dān)保,如果擔(dān)保失敗會(huì)執(zhí)行一次 Full GC(和4應(yīng)該是一個(gè)意思)

    閱讀參考

    MinorGC、MajorGC和FullGC的對(duì)比

    Stop the World-STW

    • STW指的是GC事件發(fā)生過(guò)程中,會(huì)停止用戶的所有線程,整個(gè)應(yīng)用程序就像卡死一樣,沒(méi)有任何響應(yīng)
    • STW是為了確保對(duì)象可達(dá)性分析的準(zhǔn)確性。如果對(duì)象在可達(dá)性分析過(guò)程中引用關(guān)系還在變化,則會(huì)導(dǎo)致分析的結(jié)果不準(zhǔn)確
    • STW與采用哪款GC器無(wú)關(guān),所有的GC器都有這個(gè)事件
    • STW是在JVM后臺(tái) 自動(dòng)發(fā)起 和 自動(dòng)完成的
    • 開(kāi)發(fā)中不要使用System.gc(),會(huì)導(dǎo)致STW

    System.gc()

    • 顯示調(diào)用System.gc() 會(huì)建議 垃圾收集器進(jìn)行Full GC,注意只是建議,并不一定會(huì)真的進(jìn)行Full GC

    內(nèi)存溢出和內(nèi)存泄漏(重要)

    • 內(nèi)存溢出:內(nèi)存都被對(duì)象占滿了,沒(méi)有足夠空間分配給新的對(duì)象

    • 內(nèi)存泄露:內(nèi)存泄漏 就是 申請(qǐng)了之后 沒(méi)法釋放(用完了不歸還)

    • 內(nèi)存溢出要么是程序有問(wèn)題導(dǎo)致的,要么就是分配的內(nèi)存不夠?qū)е碌?/p>

    • 內(nèi)存溢出出現(xiàn)的兩個(gè)原因:

    • 堆內(nèi)存設(shè)置不夠
    • 代碼中創(chuàng)建了大量大對(duì)象,并且長(zhǎng)時(shí)間不能被垃圾收集器回收(存在引用)
    • 在拋出OOM之前,一般都會(huì)進(jìn)行一次垃圾回收,盡可能的去清理出空間(例如嘗試回收軟引用)。當(dāng)然也不是在任何情況下垃圾回收都會(huì)被觸發(fā)。例如分配的對(duì)象的大小超過(guò)了堆的最大空間,就會(huì)直接拋出OOM

    • 只有 對(duì)象 不會(huì)再被程序使用,但是 GC又不能回收 它們的情況,就叫內(nèi)存泄漏,就是上面說(shuō)的用完了不還

    • 內(nèi)存泄漏可能會(huì)導(dǎo)致內(nèi)存溢出。當(dāng)內(nèi)存泄漏越來(lái)越多,逐步蠶食整個(gè)內(nèi)存,直至耗盡所有內(nèi)存,就會(huì)導(dǎo)致OOM

    • 內(nèi)存泄漏例子:

    • 單例模式返回的對(duì)象 引用了另一個(gè)對(duì)象,被引用 的 對(duì)象 存在內(nèi)存泄漏
    • 數(shù)據(jù)庫(kù)連接、IO連接的對(duì)象沒(méi)有手動(dòng)close,不能被回收,會(huì)存在內(nèi)存泄漏

    閱讀參考

    System.gc()的理解、內(nèi)存溢出與內(nèi)存泄漏、Stop the World

    垃圾回收算法


    從如何判定對(duì)象消亡的角度出發(fā),垃圾收集算法 可以劃分為 “引用計(jì)數(shù)式垃圾收集”(Reference Counting GC)和 “追蹤式垃圾收集”(Tracing GC)兩大類,這兩類也常被稱作“直接垃圾收集”和“間接垃圾收集”

    標(biāo)記-清除算法(Mark-Sweep)

    • 標(biāo)記清除算法是最基礎(chǔ)的垃圾回收算法,其過(guò)程分為 標(biāo)記和 清除 兩個(gè)階段
    • 在標(biāo)記階段標(biāo)記所有需要回收的對(duì)象
    • 在清除階段清除可回收的對(duì)象并釋放其所占用的內(nèi)存空間
    • 效率不算高(標(biāo)記階段 需要 遞歸遍歷 找出 可達(dá)對(duì)象,清除階段 需要 線性遍歷堆中 所有對(duì)象,清除垃圾對(duì)象),如果Java堆中包含大量對(duì)象,而且其中大部分是需要被回收的,這時(shí)必須進(jìn)行大量標(biāo)記和清除的動(dòng)作,導(dǎo)致標(biāo)記和清除兩個(gè)過(guò)程的執(zhí)行效率都隨對(duì)象數(shù)量增長(zhǎng)而降低
    • 在進(jìn)行GC的時(shí)候,需要停止整個(gè)應(yīng)用程序(STW),導(dǎo)致用戶體驗(yàn)差
    • 由于標(biāo)記清除算法在清理對(duì)象所占用的內(nèi)存空間后并沒(méi)有重新整理可用的內(nèi)存空間,因此如果內(nèi)存中可被回收的小對(duì)象居多,則會(huì)引起內(nèi)存碎片化的問(wèn)題,繼而引起 大對(duì)象 無(wú)法獲得連續(xù)可用空間,而不得不 提前出發(fā)另一次垃圾收集動(dòng)作
    • 該算法一般在老年代中使用

    標(biāo)記-復(fù)制算法(Copying)

    • 標(biāo)記復(fù)制算法(Copying)是在標(biāo)記清除算法上演化而來(lái),解決標(biāo)記清除算法的內(nèi)存碎片問(wèn)題
    • 它將 可用內(nèi)存 按容量 劃分為 大小相等的兩塊,每次 只使用 其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用過(guò)的內(nèi)存空間一次清理掉
    • 優(yōu)點(diǎn)是沒(méi)有標(biāo)記和清除過(guò)程,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效;復(fù)制之后保證了內(nèi)存的連續(xù)可用,內(nèi)存分配時(shí)也就不用再考慮內(nèi)存碎片等復(fù)雜情況
    • 缺點(diǎn)是只能有一半內(nèi)存空間來(lái)被使用;復(fù)制對(duì)象之后,需要修改棧中對(duì)象的引用變量地址(因?yàn)閷?duì)象被復(fù)制到了另外的空間),復(fù)制對(duì)象也會(huì)消耗不少的時(shí)間
    • 如果內(nèi)存中多數(shù)對(duì)象都是存活的,這種算法 將會(huì)產(chǎn)生 大量的 內(nèi)存間復(fù)制 的 開(kāi)銷,但對(duì)于多數(shù)對(duì)象都是可回收的情況,算法 需要復(fù)制的 就是 占少數(shù)的存活對(duì)象,而且每次 都是針對(duì) 整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,分配內(nèi)存時(shí) 也就不用考慮 有空間碎片的復(fù)雜情況,只要移動(dòng)堆頂指針,按順序分配即可
    • 此種方法被用于新生代的垃圾回收上,如果另外一塊survivor空間 沒(méi)有足夠空間 存放上一次新生代收集下來(lái)的存活對(duì)象,這些對(duì)象將直接通過(guò) 分配擔(dān)保機(jī)制 進(jìn)入老年代

    標(biāo)記-整理算法(Mark-Compact)

    • 標(biāo)記整理算法結(jié)合了標(biāo)記清除算法和復(fù)制算法的優(yōu)點(diǎn),其標(biāo)記階段和標(biāo)記清除算法的標(biāo)記階段相同,在標(biāo)記完成后將存活的對(duì)象移到內(nèi)存的一端,然后再清理掉 端邊界以外的內(nèi)存區(qū)域
    • 標(biāo)記整理算法一方面在標(biāo)記-清除算法上做了升級(jí),解決了內(nèi)存碎片的問(wèn)題,也規(guī)避了復(fù)制算法只能利用一半內(nèi)存區(qū)域的弊端
    • 優(yōu)點(diǎn):被標(biāo)記的存活的對(duì)象會(huì)被整理,按照內(nèi)存地址依次排列,而未被標(biāo)記的內(nèi)存會(huì)被清理掉。當(dāng)需要給新對(duì)象分配內(nèi)存時(shí),JVM只需要維護(hù)一個(gè)可用內(nèi)存的起始地址就可以,比維護(hù)一個(gè)空閑列表少了許多開(kāi)銷
    • 缺點(diǎn):對(duì)內(nèi)存變動(dòng)更頻繁,需要整理所有存活對(duì)象的引用地址,在效率上比復(fù)制算法要差很多,移動(dòng)過(guò)程中需要STW,尤其是在老年代這種每次回收都有大量對(duì)象存活區(qū)域,移動(dòng)存活對(duì)象并更新所有引用這些對(duì)象的地方將會(huì)是一種極為負(fù)重的操作,而且這種對(duì)象移動(dòng)操作必須全程暫停用戶應(yīng)用程序才能進(jìn)行
    • 該算法一般在老年代中使用

    分代收集算法(Generational Collecting)

    無(wú)論是標(biāo)記清除算法、復(fù)制算法還是標(biāo)記整理算法,都無(wú)法對(duì)所有類型(長(zhǎng)生命周期、短生命周期、大對(duì)象、小對(duì)象)的對(duì)象都進(jìn)行垃圾回收。因此,針對(duì)不同的對(duì)象類型,JVM采用了不同的垃圾回收算法,該算法被稱為分代收集算法

    • 分代收集算法 根據(jù)對(duì)象的不同類型 將內(nèi)存 劃分為不同的區(qū)域,JVM將堆劃分為新生代和老年代,新生代主要存放新生成的對(duì)象,其特點(diǎn)是對(duì)象數(shù)量多但是生命周期短,在每次進(jìn)行垃圾回收時(shí)都有大量的對(duì)象被回收;老年代主要存放大對(duì)象和生命周期長(zhǎng)的對(duì)象,因此可回收的對(duì)象相對(duì)較少。JVM根據(jù)不同的區(qū)域?qū)ο蟮奶攸c(diǎn)選擇了不同的算法
    • 目前大部分JVM在新生代采用復(fù)制算法,因?yàn)樵谛律忻看芜M(jìn)行垃圾回收時(shí)都有大量的對(duì)象被回收,需要復(fù)制的對(duì)象(存活的對(duì)象)較少,不存在大量的對(duì)象在內(nèi)存中被來(lái)回復(fù)制的問(wèn)題,因此采用復(fù)制算法能安全、高效地回收新生代大量的短生命周期的對(duì)象并釋放內(nèi)存
    • JVM將新生代進(jìn)一步劃分為一塊較大的Eden區(qū)和兩塊較小的Servivor區(qū),Servivor區(qū)又分為ServivorFrom區(qū)和ServivorTo區(qū)。JVM在運(yùn)行過(guò)程中主要使用Eden區(qū)和ServivorFrom區(qū),進(jìn)行垃圾回收時(shí)會(huì)將在Eden區(qū)和ServivorFrom區(qū)中存活的對(duì)象復(fù)制到ServivorTo區(qū),然后清理Eden區(qū)和ServivorFrom區(qū)的內(nèi)存空間
    • 老年代主要存放生命周期較長(zhǎng)的對(duì)象和大對(duì)象,因而每次只有少量非存活的對(duì)象被回收,因而在老年代采用標(biāo)記整理算法
    • 在JVM中還有一個(gè)區(qū)域,即方法區(qū)(永久代),永久代用來(lái)存儲(chǔ)Class類、常量、方法描述等。在永久代 主要回收 廢棄的常量和無(wú)用的類
    • JVM內(nèi)存中的對(duì)象主要被分配到新生代的Eden區(qū)和ServivorFrom區(qū),在少數(shù)情況下會(huì)被直接分配到老年代。在新生代的Eden區(qū)和ServivorFrom區(qū)的內(nèi)存空間不足時(shí)會(huì)觸發(fā)一次GC,該過(guò)程被稱為MinorGC。在MinorGC后,在Eden區(qū)和ServivorFrom區(qū)中存活的對(duì)象會(huì)被復(fù)制到ServivorTo區(qū),然后Eden區(qū)和ServivorFrom區(qū)被清理。如果此時(shí)在ServivorTo區(qū)無(wú)法找到連續(xù)的內(nèi)存空間存儲(chǔ)某個(gè)對(duì)象,則將這個(gè)對(duì)象直接存儲(chǔ)到老年代。若Servivor區(qū)的對(duì)象經(jīng)過(guò)一次GC后仍然存活,則其年齡加 1。在默認(rèn)情況下,對(duì)象在年齡達(dá)到15時(shí),將被移到老年代

    分區(qū)收集算法

    • 分區(qū)算法 將整個(gè)堆空間 劃分為 連續(xù)的 大小不同的 小區(qū)域(Region),對(duì)每個(gè)小區(qū)域都單獨(dú)進(jìn)行內(nèi)存使用和垃圾回收,好處是 可以根據(jù) 每個(gè)小區(qū)域內(nèi)存的大小 靈活使用 和 釋放內(nèi)存
    • 分區(qū)收集算法 可以根據(jù) 系統(tǒng)可接受的停頓時(shí)間,每次快速回收若干個(gè)小區(qū)域的內(nèi)存,以縮短 垃圾回收時(shí)系統(tǒng)停頓的時(shí)間,最后 以多次并行累加的方式 逐步完成 整個(gè)內(nèi)存區(qū)域的垃圾回收
    • 如果垃圾回收機(jī)制一次回收整個(gè)堆內(nèi)存,則需要更長(zhǎng)的系統(tǒng)停頓時(shí)間,長(zhǎng)時(shí)間的系統(tǒng)停頓將影響系統(tǒng)運(yùn)行的穩(wěn)定性
    • G1垃圾收集器采用的就是這種算法

    增量收集算法(Incremental Collecting)

    • 在上述的收集算法中,每次垃圾回收,應(yīng)用程序都會(huì)處于一種Stop the World的狀態(tài),這種狀態(tài)下,應(yīng)用程序會(huì)被掛起,暫停一切正常的工作,這樣一來(lái),將嚴(yán)重影響用戶體驗(yàn)或者系統(tǒng)穩(wěn)定性
    • 如果一次性將所有的垃圾進(jìn)行處理,需要造成系統(tǒng)長(zhǎng)時(shí)間的停頓,那么就可以讓垃圾收集線程和應(yīng)用程序線程交替進(jìn)行垃圾回收線程 每次 只收集 一小片區(qū)域的內(nèi)存空間,接著 切換到 用戶線程繼續(xù)執(zhí)行,依次反復(fù),直到垃圾收集完成
    • 增量收集算法的基礎(chǔ) 仍然是標(biāo)記-清除 和 復(fù)制算法,增量收集算法 通過(guò) 對(duì)線程間沖突的 妥善處理,允許垃圾收集線程 以分階段的方式 完成標(biāo)記、清理或復(fù)制工作
    • 缺點(diǎn):線程切換 和 上下文轉(zhuǎn)換的消耗,會(huì)使得垃圾回收整體成本上升,造成系統(tǒng)吞吐量的下降

    閱讀參考

    • 垃圾清除階段-標(biāo)記-清除算法、復(fù)制算法、標(biāo)記-壓縮算法 包含3中算法的對(duì)比
    • 分代收集算法、增量收集算法、分區(qū)收集算法簡(jiǎn)介

    GC性能指標(biāo)

    • 吞吐量 = 運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間))
    • 暫停時(shí)間,執(zhí)行垃圾收集時(shí),用戶線程被暫停的時(shí)間
    • 內(nèi)存占用,Java堆所占的內(nèi)存大小

    隨著內(nèi)存的發(fā)展,占用更多的內(nèi)存變得越來(lái)越能夠容忍,這樣一來(lái),垃圾回收進(jìn)行的次數(shù)少,程序運(yùn)行時(shí)間就長(zhǎng),吞吐量就高;但是當(dāng)需要進(jìn)行垃圾回收的時(shí)候,由于需要清理的對(duì)象太多,用戶線程暫停的時(shí)間就會(huì)很長(zhǎng),延遲就會(huì)很高

    吞吐量 和 暫停時(shí)間 是相互矛盾的
    追求高吞吐量,必然會(huì)降低內(nèi)存回收的執(zhí)行頻率,但是這樣,GC會(huì)需要更長(zhǎng)的暫停時(shí)間來(lái)執(zhí)行內(nèi)存回收。
    追求低延遲,也就是降低每次執(zhí)行內(nèi)存回收的暫停時(shí)間,必然會(huì)頻繁的執(zhí)行內(nèi)存回收,這樣會(huì)造成吞吐量降低,因?yàn)?strong>用戶線程執(zhí)行的時(shí)間變少了

    閱讀參考

    垃圾回收器的分類、GC性能指標(biāo)、吞吐量與暫停時(shí)間對(duì)比

    幾個(gè)概念(下面的垃圾收集器中會(huì)遇到)

    并行-Parallel 與 并發(fā)(重要!!!)

    • 垃圾回收并行,指的是 多條 垃圾收集(的) 線程 并行(在多個(gè)CPU上)工作,但此時(shí) 用戶線程 處于等待狀態(tài)
    • 垃圾回收并發(fā),指的是 用戶線程 和 垃圾回收線程 同時(shí)執(zhí)行,但不一定是并行的,可能會(huì)交替執(zhí)行,垃圾回收線程在執(zhí)行時(shí) 不會(huì)停頓 用戶程序的執(zhí)行

    根節(jié)點(diǎn) 枚舉(動(dòng)詞)

    固定可作為GC Roots的節(jié)點(diǎn) 主要在 全局性的引用(例如常量或類靜態(tài)屬性)與 執(zhí)行上下文(例如棧幀中的本地變量表)中,盡管目標(biāo)明確,但 查找過(guò)程 要做到高效 并非一件容易的事情,現(xiàn)在Java應(yīng)用越做越龐大,光是方法區(qū)的大小就常有數(shù)百上千兆,里面的類、常量等更是恒河沙數(shù),若要逐個(gè)檢查以這里為起源的引用肯定得消耗不少時(shí)間

    迄今為止,所有收集器 在 根節(jié)點(diǎn)枚舉 這一步驟時(shí) 都是必須暫停用戶線程的,因此 毫無(wú)疑問(wèn) 根節(jié)點(diǎn)枚舉 與 之前提及的整理內(nèi)存碎片 一樣會(huì)面臨 相似的“Stop The World”的困擾
    現(xiàn)在 可達(dá)性分析算法 耗時(shí)最長(zhǎng) 的 查找引用鏈的過(guò)程 已經(jīng)可以做到 與 用戶線程一起并發(fā),但 根節(jié)點(diǎn)枚舉 始終還是 必須在 一個(gè) 能保障一致性的快照中 才得以進(jìn)行,這里“一致性”的意思是 整個(gè)枚舉期間 執(zhí)行子系統(tǒng) 看起來(lái) 就像被 凍結(jié)在某個(gè)時(shí)間點(diǎn)上,不會(huì)出現(xiàn) 分析過(guò)程中,根節(jié)點(diǎn)集合 的 對(duì)象引用關(guān)系 還在 不斷變化的情況,若這點(diǎn)不能滿足的話,分析結(jié)果準(zhǔn)確性也就無(wú)法保證。這是導(dǎo)致 垃圾收集過(guò)程 必須停頓所有用戶線程 的 其中一個(gè)重要原因,即使是 號(hào)稱 停頓時(shí)間可控,或者(幾乎)不會(huì)發(fā)生停頓的CMS、G1、ZGC等收集器,枚舉根節(jié)點(diǎn)時(shí)也是必須要停頓的

    由于目前主流Java虛擬機(jī)使用的都是準(zhǔn)確式垃圾收集,所以當(dāng)用戶線程停頓下來(lái)之后,其實(shí)并不需要一個(gè)不漏地檢查完 所有執(zhí)行上下文 和 全局的引用位置,虛擬機(jī)應(yīng)當(dāng)是有辦法直接得到哪些地方存放著對(duì)象引用的
    在HotSpot的解決方案里,是 使用一組 稱為 OopMap的數(shù)據(jù)結(jié)構(gòu)來(lái)達(dá)到這個(gè)目的

    一旦類加載動(dòng)作完成的時(shí)候,HotSpot 就會(huì)把 對(duì)象內(nèi) 什么偏移量上是什么類型的數(shù)據(jù) 計(jì)算出來(lái),也會(huì)在 特定的位置 記錄下 棧里和寄存器里 哪些位置是引用
    這樣收集器在掃描時(shí) 就可以 直接得知這些信息了,并不需要真正一個(gè)不漏地從方法區(qū)等GC Roots開(kāi)始查找

    安全點(diǎn)

    • 可達(dá)性分析 對(duì) 執(zhí)行時(shí)間的敏感 體現(xiàn)在GC停頓上,因?yàn)檫@項(xiàng)分析工作 必須在一個(gè)能確保“一致性”的快照中進(jìn)行,“一致性”的意思是指 整個(gè)分析期間 整個(gè)執(zhí)行系統(tǒng) 看起來(lái)就像 被凍結(jié)在某個(gè)時(shí)間點(diǎn)上,不可以出現(xiàn) 分析過(guò)程中 對(duì)象引用關(guān)系 還在不斷變化的情況,要不就會(huì)出現(xiàn) 分析結(jié)果準(zhǔn)確定 無(wú)法得到保證
    • 在某一瞬間,對(duì)JVM中所有的對(duì)象引用情況分析一遍是不現(xiàn)實(shí)的。在HotSpot中,使用一組稱為OopMap的數(shù)據(jù)結(jié)構(gòu) 來(lái)達(dá)到 “哪些地方存著對(duì)象引用” 這個(gè)目的。在 類加載完的時(shí)候,HotSpot就把 對(duì)象內(nèi) 什么偏移量上 是什么數(shù)據(jù)類型 計(jì)算出來(lái),在JIT編譯過(guò)程中,也會(huì)在特定的位置(安全點(diǎn))記錄下 棧和寄存器中 哪些位置是引用
    • 程序執(zhí)行時(shí) 并非在所有地方都能停頓下來(lái)開(kāi)始GC,只有在到達(dá)安全點(diǎn)時(shí)才能暫停
    • 安全點(diǎn)選定標(biāo)準(zhǔn):是否具有 讓程序長(zhǎng)時(shí)間執(zhí)行 的特征長(zhǎng)時(shí)間執(zhí)行最明顯的特征:指令序列復(fù)用,例如方法調(diào)用、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)等
    • 另一個(gè)需要考慮的問(wèn)題是,如何在GC發(fā)生時(shí) 讓所有的線程都“跑”到最近的 安全點(diǎn)上 再停頓下來(lái)。兩種方案:搶先式中斷、主動(dòng)式中斷
    • 搶先式中斷:在GC發(fā)生時(shí),把所以線程全部中斷,如果發(fā)現(xiàn)有線程中斷的地方 不在安全點(diǎn)上,就恢復(fù)線程讓它“跑”到安全點(diǎn)上
    • 主動(dòng)式中斷:當(dāng)GC需要中斷線程時(shí),不直接對(duì)線程操作,僅僅簡(jiǎn)單的設(shè)置一個(gè)標(biāo)志,各個(gè)線程執(zhí)行時(shí)主動(dòng)去輪詢這個(gè)標(biāo)志,發(fā)現(xiàn)中斷標(biāo)志位真時(shí)就自己中斷掛起。輪詢標(biāo)志的地方和安全點(diǎn)是重合的

    安全區(qū)域

    • 安全點(diǎn)機(jī)制 保證了程序執(zhí)行時(shí),在不太長(zhǎng)的時(shí)間內(nèi)就會(huì)遇到可進(jìn)入GC的安全點(diǎn)。但是在程序不執(zhí)行的時(shí)候呢?不執(zhí)行就是沒(méi)有分配CPU時(shí)間,典型的例子就是線程處于Sleep狀態(tài)或者Blocked狀態(tài),此時(shí)就需要安全區(qū)域來(lái)解決
    • 安全區(qū)域 是指 在一段代碼片段中,引用關(guān)系不會(huì)發(fā)生變化。在這個(gè)區(qū)域中的任意地方開(kāi)始GC都是安全的
    • 線程執(zhí)行到安全區(qū)域的代碼時(shí),首先標(biāo)識(shí)自己已經(jīng)進(jìn)入了安全區(qū)域;在線程要離開(kāi)安全區(qū)域時(shí),要檢查 系統(tǒng)是否已經(jīng)完成了 根節(jié)點(diǎn)枚舉,如果完成了,線程就繼續(xù)執(zhí)行,否則,就必須等待,知道收到可以安全離開(kāi) 安全區(qū)域的信號(hào)為止

    閱讀參考

    • 垃圾回收的并行與并發(fā)、安全點(diǎn)與安全區(qū)域

    垃圾收集器

    垃圾收集器分類

    • 按垃圾收集器的線程數(shù)分類:分為 串行 和 并行 垃圾收集器,串行垃圾收集器 只有 一個(gè)垃圾回收線程;并行垃圾收集器 有多個(gè) 垃圾回收線程
    • 串行以及并行垃圾收集器在回收垃圾的時(shí)候,都會(huì)Stop the World
    • 按工作模式分類:分為 并發(fā)式 和 獨(dú)占式 垃圾收集器,獨(dú)占式 指的是 停止用戶線程,直到垃圾回收完成;并發(fā)式 指的是 垃圾收集器線程 和 應(yīng)用程序線程 交替工作,盡可能的減少應(yīng)用程序的停頓時(shí)間
    • 按碎片處理方式分類:分為 壓縮式 和 非壓縮式 垃圾收集器,壓縮式 指的是 會(huì)對(duì)內(nèi)存進(jìn)行碎片整理;非壓縮式 指的是 不對(duì)內(nèi)存進(jìn)行內(nèi)存整理
    • 對(duì)于經(jīng)過(guò)內(nèi)存碎片整理的空間,再分配對(duì)象空間的時(shí)候使用的是指針碰撞法;對(duì)于沒(méi)有經(jīng)過(guò)碎片整理的空間,再分配對(duì)象空間的時(shí)候使用的是空閑列表法
    • 按工作內(nèi)存區(qū)間分類:分為年輕代和老年代垃圾收集器

    Serial垃圾收集器

    特點(diǎn)

    • 最基本,發(fā)展歷史最悠久的收集器
    • 單線程收集器,在它進(jìn)行垃圾收集時(shí),必須暫停其他所有的用戶線程(STW),直到它收集結(jié)束
    • 以 串行 方式執(zhí)行
    • 使用復(fù)制算法,用于新生代
    • Serial垃圾收集器 是 JVM運(yùn)行在Client模式下 的 新生代默認(rèn)垃圾收集器

    參數(shù)

    -XX:+UseSerialGC= Serial + SerialOld

    優(yōu)勢(shì)

    Serial有著優(yōu)于其他收集器的地方,那就是簡(jiǎn)單而高效(與其他收集器的單線程相比)
    對(duì)于內(nèi)存資源受限的環(huán)境,它是所有收集器里額外內(nèi)存消耗(Memory Footprint)最小的
    對(duì)于單核處理器 或 處理器核心數(shù)較少的環(huán)境來(lái)說(shuō),Serial收集器 由于沒(méi)有 線程交互的開(kāi)銷,專心做垃圾收集自然可以獲得最高的單線程收集效率

    應(yīng)用場(chǎng)景

    在用戶桌面的應(yīng)用場(chǎng)景以及部分微服務(wù)應(yīng)用中,分配給虛擬機(jī)管理的內(nèi)存一般來(lái)說(shuō)并不會(huì)特別大,收集幾十兆甚至一兩百兆的新生代(僅僅是指新生代使用的內(nèi)存,桌面應(yīng)用甚少超過(guò)這個(gè)容量),垃圾收集的停頓時(shí)間完全可以控制在十幾、幾十毫秒,最多一百多毫秒以內(nèi),只要不是頻繁發(fā)生收集,這點(diǎn)停頓時(shí)間對(duì)許多用戶來(lái)說(shuō)是完全可以接受的。所以,Serial收集器對(duì)于運(yùn)行在客戶端模式下的虛擬機(jī)來(lái)說(shuō)是一個(gè)很好的選擇

    Serial Old(老年代)垃圾收集器

    特點(diǎn)

    • Serail收集器的老年代版本
    • 單線程收集器,“單線程”的意義不僅是 只會(huì)使用一個(gè)處理器 或 一條收集線程 去完成 垃圾收集工作,更重要的是強(qiáng)調(diào)在 它進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程,直到它收集結(jié)束
    • 以 串行 方式執(zhí)行
    • 使用標(biāo)記-整理算法,用于老年代
    • Serial Old 是 JVM運(yùn)行在Client模式下 的 老年代默認(rèn)垃圾收集器
    • 在HotSpot虛擬機(jī)的Server模式下,Serial Old有兩個(gè)用途
    • 在JDK 5以及之前的版本中與新生代的Parallel Scavenge配合使用
    • 作為老年代CMS收集器的后備垃圾收集方案,在并發(fā)收集發(fā)生Concurrent Mode Failure時(shí)使用

    Serial+ Serial Old組合收集器運(yùn)行示意圖如下:

    參數(shù)

    -XX:+UseSerialGC= Serial + SerialOld

    Par(Parallel)New(New表示只能處理新生代)垃圾收集器

    特點(diǎn)

    • ParNew 是 Serial收集器 的 多線程版本,雖然是多線程,但仍需要STW
    • 收集器可用的控制參數(shù)、收集算法(復(fù)制)、STW、對(duì)象分配規(guī)則、回收策略等于Serial完全一樣
    • 以 并行 方式執(zhí)行(多個(gè)GC線程)
    • 使用復(fù)制算法,用于新生代,垃圾回收次數(shù)比較頻繁,使用 并行(條垃圾收集器線程) 回收的方式,效率高
    • ParNew 是 激活CMS后(使用 -XX:+UseConcMarkSweepGC) 的 默認(rèn)新生代收集器
    • ParNew 是 Server模式下的 虛擬機(jī)首選 新生代收集器
    • ParNew垃圾收集器 默認(rèn)開(kāi)啟 與 CPU同等數(shù)量 的線程 進(jìn)行垃圾回收

    ParNew + Serial Old組合收集器運(yùn)行示意圖如下:

    參數(shù)

    • -XX:+UseParNewGC = ParNew + Serilal Old,指定使用ParNew
    • 在Java應(yīng)用啟動(dòng)時(shí)可通過(guò)-XX:ParallelGCThreads參數(shù)調(diào)節(jié)垃圾收集的線程數(shù)量
    • -XX:+UseConcMarkSweepGC,指定使用CMS后,會(huì)默認(rèn)使用ParNew作為新生代收集器

    應(yīng)用場(chǎng)景

    • 目前除Serial外,只有 ParNew 能與 CMS(在老年代)收集器 配合工作
    • 在單個(gè)CPU環(huán)境中,不會(huì)比Serail收集器有更好的效果,因?yàn)榇嬖诰€程交互開(kāi)銷

    為什么只有ParNew能與CMS收集器配合?

    • CMS是HotSpot在JDK1.5推出的第一款真正意義上的并發(fā)(Concurrent)收集器,第一次實(shí)現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時(shí)工作
    • CMS作為老年代收集器,但卻無(wú)法與JDK1.4已經(jīng)存在的新生代收集器Parallel Scavenge配合工作,因?yàn)镻arallel Scavenge(以及G1)都沒(méi)有使用傳統(tǒng)的GC收集器代碼框架,而另外獨(dú)立實(shí)現(xiàn),而其余幾種收集器則共用了部分的框架代碼

    Parallel Scavenge垃圾收集器(吞吐量?jī)?yōu)先收集器,新生代)

    特點(diǎn)

    • Parallel Scavenge收集器 是為提高 新生代垃圾收集效率 而設(shè)計(jì)的 垃圾收集器,它的關(guān)注點(diǎn) 是 達(dá)到一個(gè) 可控制的吞吐量(上面已經(jīng)整理過(guò));而 CMS等收集器的關(guān)注點(diǎn) 是 盡可能地 縮短 垃圾收集時(shí) 用戶線程的停頓時(shí)間(不同點(diǎn))
    • 多線程收集器,雖然是多線程,但仍需要STW
    • 以 并行 方式執(zhí)行(多個(gè)GC線程)
    • 使用復(fù)制算法,用于新生代
    • Parallel Scavenge 是 Server模式下的 虛擬機(jī)默認(rèn)垃圾收集器

    參數(shù)

    • -XX:MaxGCPauseMillis,控制 最大 垃圾收集停頓時(shí)間,一個(gè)大于0的毫秒數(shù),-XX:MaxGCPauseMillis設(shè)置的稍小,停頓時(shí)間可能會(huì)縮短,但也可能會(huì)使得吞吐量下降,因?yàn)榭赡軐?dǎo)致垃圾收集發(fā)生得更頻繁
    • -XX:GCTimeRatio,控制吞吐量大小,大于0小于100的整數(shù),是垃圾收集時(shí)間 占總時(shí)間的比率
    • -XX:+UseAdaptiveSizePolicy 自適應(yīng)調(diào)節(jié)策略開(kāi)啟,當(dāng)這個(gè)參數(shù)打開(kāi)之后,不需要手工指定 新生代大小、Eden與Survivor比例、晉升到老年代對(duì)象年齡代數(shù)。JVM會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集信息,動(dòng)態(tài)調(diào)整以提供最合適的 停頓時(shí)間 或 最大吞吐量,這種調(diào)節(jié)方式稱為垃圾收集的自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics)
    • -XX:+UseParallelGC(默認(rèn)) = Parallel Scavenge + Parallel Old(JDK 1.8默認(rèn))

    應(yīng)用場(chǎng)景

    停頓時(shí)間越短 就越適合 需要與用戶交互的程序,良好的響應(yīng)速度能提升用戶體驗(yàn)
    而 高吞吐量 則可以 高效率地利用CPU時(shí)間(減少垃圾回收所耗費(fèi)的時(shí)間),讓用戶代碼獲得更長(zhǎng)的運(yùn)行時(shí)間
    Parallel Scavenge適合那種交互少、運(yùn)算多的場(chǎng)景,例如:執(zhí)行批量處理、訂單處理、工資支付、科學(xué)計(jì)算的應(yīng)用程序

    Parallel Old垃圾收集器

    特點(diǎn)

    • Parallel Scavenge收集器的老年代版本
    • 多線程收集器,雖然是多線程,但仍需要STW
    • 以 并行 方式執(zhí)行(多個(gè)GC線程)
    • 使用標(biāo)記-整理算法

    Parallel Old垃圾收集器 在設(shè)計(jì)上 優(yōu)先考慮系統(tǒng)吞吐量,其次考慮停頓時(shí)間等因素
    如果系統(tǒng)對(duì)吞吐量的要求較高,則可以優(yōu)先考慮新生代的Parallel Scavenge垃圾收集器 + 老年代的Parallel Old垃圾收集器的配合使用,如下圖:

    參數(shù)

    • -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old(該參數(shù)在JDK1.5之后已無(wú)用)
    • -XX:-UseParallelOldGC = Parallel Scavenge + Serial Old

    注意參數(shù)一個(gè)是+,一個(gè)是-,對(duì)應(yīng)的是老年代使用的收集器不一樣

    CMS(Concurrent Mark Sweep)垃圾收集器(重要!!!)

    特點(diǎn)

    • 為 老年代 設(shè)計(jì)的 垃圾收集器,主要目的是達(dá)到 獲取 最短回收停頓時(shí)間(低延遲) ,以便 在多線程并發(fā)環(huán)境下 以最短的垃圾收集停頓時(shí)間 提高系統(tǒng)的穩(wěn)定性
    • 多線程收集器
    • 在并發(fā)標(biāo)記 和 并發(fā)清除階段 以 并發(fā) 方式執(zhí)行(GC線程 和 用戶線程 同時(shí)進(jìn)行);在重新標(biāo)記階段 以 并行 方式執(zhí)行(多個(gè)GC線程)
    • 使用標(biāo)記-清除算法,不進(jìn)行整理壓縮,會(huì)產(chǎn)生內(nèi)存碎片,用于老年代
    • CMS 只能和 Serial收集器 以及 ParNew收集器組合使用,這兩個(gè)都是新生代的收集器
    • CMS不能等到內(nèi)存空間不夠的時(shí)候,再去進(jìn)行垃圾回收,需要提前進(jìn)行垃圾回收。因?yàn)槔厥盏臅r(shí)候和用戶線程并發(fā)執(zhí)行,如果,內(nèi)存空間不夠了,再去進(jìn)行垃圾回收,用戶線程和垃圾回收線程都沒(méi)有足夠的空間可用了

    缺點(diǎn)(重要!!!)

    • 對(duì)CPU資源非常敏感,并發(fā)標(biāo)記 雖然不會(huì)暫停用戶線程,但因?yàn)檎加靡徊糠諧PU資源,還是會(huì)導(dǎo)致應(yīng)用程序變慢,總吞吐量降低。CMS默認(rèn)啟動(dòng)的回收線程數(shù) 是 (cpu個(gè)數(shù)+3)/4。當(dāng)CPU數(shù)量多于4個(gè),收集線程占用的CPU資源多于25%,對(duì)用戶程序影響可能較大;不足4個(gè)時(shí),影響更大,可能無(wú)法接受。針對(duì)這種情況,曾出現(xiàn)了"增量式并發(fā)收集器"(Incremental Concurrent Mark Sweep/i-CMS),類似使用搶占式來(lái)模擬多任務(wù)機(jī)制的思想,讓收集線程和用戶線程交替運(yùn)行,減少收集線程運(yùn)行時(shí)間,但效果并不理想,JDK1.6后就官方不再提倡用戶使用
    • 無(wú)法處理浮動(dòng)垃圾,浮動(dòng)垃圾是指并發(fā)清除階段由于用戶線程繼續(xù)運(yùn)行而產(chǎn)生的垃圾,CMS無(wú)法在 當(dāng)次收集中 處理掉。由于在并發(fā)清除階段用戶線程還需要運(yùn)行,使得并發(fā)清除時(shí)需要預(yù)留一定的內(nèi)存空間,意味著CMS不能像其他收集器在老年代幾乎填滿再進(jìn)行收集(也要可以認(rèn)為CMS所需要的空間比其他垃圾收集器大)。可以通過(guò) -XX:CMSInitiatingOccupancyFraction 設(shè)置CMS預(yù)留內(nèi)存空間,JDK1.5默認(rèn)值為68%、JDK1.6變?yōu)榇蠹s92%
    • Concurrent Mode Failure失敗, 接著上面說(shuō)到的預(yù)留一定的內(nèi)存空間,如果CMS預(yù)留內(nèi)存空間無(wú)法滿足程序需要,就會(huì)出現(xiàn)一次"Concurrent Mode Failure"失敗,這時(shí)JVM啟用后備預(yù)案:臨時(shí)啟用Serail Old收集器,而導(dǎo)致另一次Full GC的產(chǎn)生,這樣的代價(jià)是很大的,所以 -XX:CMSInitiatingOccupancyFraction 不能設(shè)置得太大
    • 產(chǎn)生大量?jī)?nèi)存碎片,這是由于CMS基于"標(biāo)記-清除"算法,清除后不進(jìn)行壓縮操作。由于空間不再連續(xù),CMS需要使用可用"空閑列表"內(nèi)存分配方式,這比簡(jiǎn)單實(shí)用"碰撞指針"分配內(nèi)存消耗大。解決內(nèi)存碎片辦法:1.-XX:+UseCMSCompactAtFullCollection, 使得CMS出現(xiàn)上面情況時(shí)不進(jìn)行Full GC,而開(kāi)啟內(nèi)存碎片的合并整理過(guò)程,但合并整理過(guò)程無(wú)法并發(fā),停頓時(shí)間會(huì)變長(zhǎng),默認(rèn)開(kāi)啟(但不會(huì)進(jìn)行,結(jié)合CMSFullGCsBeforeCompaction);2.-XX:+CMSFullGCsBeforeCompaction,設(shè)置 執(zhí)行多少次不壓縮的Full GC后,來(lái)一次壓縮整理,為減少 合并整理過(guò)程的停頓時(shí)間,默認(rèn)為0,也就是說(shuō)每次都執(zhí)行Full GC,不會(huì)進(jìn)行壓縮整理

    參數(shù)

    • -XX:+UseConc(current)MarkSweepGC = ParNew +CMS + SerialOld(給CMS備份用),手動(dòng)指定使用CMS,開(kāi)啟該參數(shù)后會(huì)自動(dòng)將-XX:+UseParNewGC打開(kāi)
    • -XX:CMSInitiatingOccupancyFraction堆內(nèi)存使用率閾值,一旦達(dá)到該閾值,便開(kāi)始回收處理,如果內(nèi)存增長(zhǎng)緩慢,可以設(shè)置的較高,降低CMS觸發(fā)頻率;如果內(nèi)存增長(zhǎng)較快,可以設(shè)置的較低,避免 頻發(fā)觸發(fā) 老年代 串行收集器
    • -XX:+UseCMSCompactAtFullCollection,默認(rèn)開(kāi)啟,用于在CMS收集器處理過(guò)程中開(kāi)啟內(nèi)存碎片整理(默認(rèn)是開(kāi)啟的,此參數(shù)從JDK 9開(kāi)始廢棄)
    • -XX:CMSFullGCsBeforeCompaction,用于 執(zhí)行了多少次 不壓縮的Full GC后,跟著來(lái)一次帶壓縮的,默認(rèn)值為0,表示每次進(jìn)入Full GC時(shí)都進(jìn)行碎片整理(此參數(shù)從JDK 9開(kāi)始廢棄)
    • -XX:ParallelCMSThreads 回收線程數(shù)

    應(yīng)用場(chǎng)景

    適用于與用戶交互較多的場(chǎng)景,希望系統(tǒng)停頓時(shí)間最短,注重服務(wù)的響應(yīng)速度,以給用戶帶來(lái)較好的體驗(yàn),如常見(jiàn)WEB、B/S系統(tǒng)的服務(wù)器上的應(yīng)用

    CMS回收過(guò)程

  • 初始標(biāo)記:僅僅只是標(biāo)記出和GC Roots 直接關(guān)聯(lián) 的對(duì)象,速度很快,單線程執(zhí)行,需要STW
  • 并發(fā)標(biāo)記:從GC Roots 的 直接關(guān)聯(lián)對(duì)象 開(kāi)始 遍歷 整個(gè)對(duì)象圖的過(guò)程,這個(gè)過(guò)程耗時(shí)長(zhǎng),不需要停頓用戶線程
  • 重新標(biāo)記 (多GC線程并行執(zhí)行):由于 在并發(fā)標(biāo)記過(guò)程中 用戶線程繼續(xù)運(yùn)行,導(dǎo)致在垃圾回收過(guò)程中部分對(duì)象的狀態(tài)發(fā)生變化,為了確保這部分對(duì)象的狀態(tài)正確性,需要對(duì)其(狀態(tài)發(fā)生變化的部分對(duì)象)重新標(biāo)記并STW,這個(gè)階段的停頓時(shí)間 比 初始標(biāo)記 稍長(zhǎng),但遠(yuǎn)比 并發(fā)標(biāo)記 短。重新標(biāo)記 就是為了 修正 在并發(fā)標(biāo)記期間 因用戶程序繼續(xù)運(yùn)作 而導(dǎo)致 標(biāo)記產(chǎn)生變動(dòng) 的 那一部分對(duì)象 的 標(biāo)記記錄。參考閱讀上面的“增量更新
  • 并發(fā)清除:和用戶線程一起工作,執(zhí)行清除GC Roots不可達(dá)對(duì)象的任務(wù),由于 不需要移動(dòng) 存活對(duì)象(會(huì)產(chǎn)生內(nèi)存碎片),所以也不需要暫停用戶線程
  • CMS垃圾收集器在和用戶線程一起工作時(shí)(并發(fā)標(biāo)記和并發(fā)清除)不需要暫停用戶線程,有效縮短了垃圾回收時(shí)系統(tǒng)的停頓時(shí)間,同時(shí)由于CMS垃圾收集器和用戶線程一起工作,因此其并行度和效率也有很大提升

    G1(Garbage-First)垃圾收集器(重要!!!)

    特點(diǎn)

    • G1(Garbage-First)是JDK7-u4才推出商用的收集器
    • 面向服務(wù)端應(yīng)用(針對(duì)具有大內(nèi)存、多處理器的機(jī)器)的垃圾收集器
    • G1 與前面的垃圾收集器有很大不同,它把新生代、老年代的劃分取消了!
    • G1 將堆內(nèi)存 劃分為 很多不相關(guān)的區(qū)域-Region(物理上可以是不連續(xù)的空間),僅是從概念保留了年輕代和老年代,實(shí)際上使用不同的Region來(lái)表示Eden,S0區(qū),S1區(qū),老年代,每個(gè)Region都可能隨G1的運(yùn)行在不同代之間切換,涉及Region的內(nèi)容在下面還有
    • G1 有計(jì)劃的 避免 在整個(gè)Java堆中 進(jìn)行 全區(qū)域的 垃圾收集,G1會(huì)跟蹤 各個(gè)Region里面的 垃圾堆積的價(jià)值大小(回收所獲得的空間大小 以及 回收所需要的時(shí)間 這兩個(gè)經(jīng)驗(yàn)值),在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間,優(yōu)先回收價(jià)值最大的Region
    • 每個(gè)Region都有一個(gè) Remembered Set,用來(lái)記錄 該Region中的對(duì)象 的 引用對(duì)象 所在的 Region,通過(guò)使用 Remembered Set,在做可達(dá)性分析的時(shí)候就可以避免全堆掃描

    參數(shù)

    • -XX:+UseG1GC
    • -XX:MaxGCPauseMillis,停頓時(shí)間,默認(rèn)200ms,如果把停頓時(shí)間調(diào)得非常低,譬如設(shè)置為20毫秒,很可能出現(xiàn)的結(jié)果就是 由于 停頓目標(biāo)時(shí)間太短,導(dǎo)致 每次選出來(lái)的回收集 只占堆內(nèi)存很小的一部分,收集器 收集的速度 逐漸 跟不上 分配器 分配的速度,導(dǎo)致垃圾慢慢堆積。很可能一開(kāi)始 收集器還能從 空閑的堆內(nèi)存中 獲得一些喘息的時(shí)間,但應(yīng)用運(yùn)行時(shí)間一長(zhǎng)就不行了,最終占滿堆引發(fā)Full GC反而降低性能,所以通常把期望停頓時(shí)間設(shè)置為一兩百毫秒或者兩三百毫秒會(huì)是比較合理的
    • -XX:ParallelGCThreads,設(shè)置 在STW的時(shí)候 并行的垃圾收集線程數(shù)量
    • -XX:ConcGCThreads 設(shè)置 用戶線程 與 垃圾收集線程 并發(fā)的時(shí)候,垃圾收集線程的線程數(shù)量
    • -XX:InitiatingHeapOccupancyPercent 設(shè)置 觸發(fā) 并發(fā)GC周期(不是普通的GC) 的 Java堆占用率的閾值,超過(guò)此值,就觸發(fā)GC,默認(rèn)是45
    • -XX:G1HeapRegionSize=n可指定分區(qū)大小(1MB~32MB,且必須是2的冪),默認(rèn)將整堆劃分為2048個(gè)分區(qū)
    • -XX:G1NewSizePercent、XX:G1MaxNewSizePercent 年輕代內(nèi)存會(huì)在 初始空間-XX:G1NewSizePercent(默認(rèn)整堆5%) 與 最大空間-XX:G1MaxNewSizePercent(默認(rèn)60%) 之間動(dòng)態(tài)變化,且由參數(shù) 目標(biāo)暫停時(shí)間-XX:MaxGCPauseMillis(默認(rèn)200ms)、需要擴(kuò)縮容的大小 以及 分區(qū)的已記憶集合(RSet)計(jì)算得到
    • G1依然可以設(shè)置固定的年輕代大小(參數(shù)-XX:NewRatio、-Xmn),但同時(shí) 暫停時(shí)間-XX:MaxGCPauseMillis 將失去意義

    優(yōu)勢(shì)

    • 并行與并發(fā):多個(gè)垃圾收集線程同時(shí)工作(并行),垃圾收集線程 與 用戶線程交替執(zhí)行(并發(fā)),其實(shí)CMS也有這個(gè)優(yōu)勢(shì)
    • 分代收集:G1將堆空間劃分為若干個(gè)區(qū)域(Region),這些區(qū)域中包含了邏輯上的年輕代和老年代。它不要求年輕代,老年代是連續(xù)的。同時(shí),G1同時(shí)兼顧年輕代和老年代的垃圾回收,其他的垃圾收集器,要么工作在年輕代,要么工作在老年代
    • 空間整合(內(nèi)存碎片整理):G1內(nèi)存回收使用Region作為基本單位,Region之間使用的是復(fù)制算法,能夠?qū)?nèi)存空間進(jìn)行整理
    • 可預(yù)測(cè)的停頓時(shí)間模型:可以讓用戶 明確指定 在一個(gè)長(zhǎng)度為M毫秒 的 時(shí)間片段內(nèi),消耗在 垃圾收集上的時(shí)間 不得超過(guò) N毫秒
    • G1從整體來(lái)看 是基于 “標(biāo)記-整理”算法實(shí)現(xiàn)的收集器,但從局部(兩個(gè)Region之間)上看又是基于“標(biāo)記-復(fù)制”算法實(shí)現(xiàn),無(wú)論如何,這兩種算法都意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片,垃圾收集完成之后能提供規(guī)整的可用內(nèi)存。這種特性有利于程序長(zhǎng)時(shí)間運(yùn)行,在程序?yàn)榇髮?duì)象分配內(nèi)存時(shí)不容易因無(wú)法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次收集

    應(yīng)用場(chǎng)景

    • 面向服務(wù)端應(yīng)用,針對(duì)具有大內(nèi)存、多處理器的機(jī)器,最主要的應(yīng)用是為需要低GC延遲,并具有大堆的應(yīng)用程序提供解決方案。如:在堆大小約6GB或更大時(shí),可預(yù)測(cè)的暫停時(shí)間可以低于0.5秒
    • 用來(lái)替換掉JDK1.5中的CMS收集器
    • 在下面的情況時(shí),使用G1可能比CMS好:
    • 超過(guò)50%的Java堆被活動(dòng)數(shù)據(jù)占用
    • 對(duì)象分配頻率或年代提升頻率變化很大
    • GC停頓時(shí)間過(guò)長(zhǎng)(長(zhǎng)于0.5至1秒)

    G1回收過(guò)程

  • 初始標(biāo)記(Initial Marking):僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,并且修改TAMS(Top at Mark Start)指針的值,讓下一階段 用戶線程 并發(fā)運(yùn)行時(shí),能正確地 在可用的Region中 分配 新對(duì)象。這個(gè)階段需要STW,但耗時(shí)很短,而且是借用 進(jìn)行Minor GC的時(shí)候 同步完成的,所以G1收集器在這個(gè)階段實(shí)際并沒(méi)有額外的停頓
  • 并發(fā)標(biāo)記(Concurrent Marking):從GC Root開(kāi)始對(duì)堆(剛才產(chǎn)生的Region集合)中對(duì)象進(jìn)行可達(dá)性分析,遞歸掃描整個(gè)堆里的對(duì)象圖,找出要回收的對(duì)象,這階段耗時(shí)較長(zhǎng),但可與用戶程序并發(fā)執(zhí)行。當(dāng)對(duì)象圖掃描完成以后,還要重新處理SATB(Snapshot-At-The-Beginning,原始快照)記錄下的 在并發(fā)時(shí) 有引用變動(dòng)的對(duì)象
  • 最終標(biāo)記(Final Marking):對(duì)用戶線程做另一個(gè)短暫的暫停STW,用于處理 并發(fā)標(biāo)記階段結(jié)束后 仍遺留下來(lái)的 最后那少量的SATB(Snapshot-At-The-Beginning,原始快照)記錄。上一階段(并發(fā)標(biāo)記)中 對(duì)象的變化 記錄在線程的Remembered Set Log中,這里(最終標(biāo)記)把Remembered Set Log合并到Remembered Set中
  • 篩選回收(Live Data Counting and Evacuation):負(fù)責(zé)更新Region的統(tǒng)計(jì)數(shù)據(jù),對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的停頓時(shí)間來(lái)制定回收計(jì)劃,可以自由選擇任意多個(gè)Region構(gòu)成回收集,然后把決定回收的那一部分Region的存活對(duì)象 復(fù)制到 空的Region中,再清理掉整個(gè)舊Region的全部空間。這里的操作涉及存活對(duì)象的移動(dòng),是必須暫停用戶線程STW,由多條收集器線程并行完成的
  • 綜上:G1收集器除了并發(fā)標(biāo)記外,其余階段都是要完全暫停用戶線程的

    G1和CMS比較-待完善(重要!!!)

    • 相比于CMS,G1還不具備全方位、壓倒性優(yōu)勢(shì)。比如,G1為了垃圾收集產(chǎn)生的內(nèi)存占用 以及 程序運(yùn)行時(shí)的額外執(zhí)行負(fù)載 都比CMS要高
    • 就內(nèi)存占用來(lái)說(shuō),雖然G1和CMS都使用卡表來(lái)處理跨代指針,但G1的卡表實(shí)現(xiàn)更為復(fù)雜,而且堆中每個(gè)Region,無(wú)論扮演的是新生代還是老年代角色,都必須有一份卡表,這導(dǎo)致G1的記憶集(和其他內(nèi)存消耗)可能會(huì)占整個(gè)堆容量的20%乃至更多的內(nèi)存空間;相比起來(lái)CMS的卡表就相當(dāng)簡(jiǎn)單,只有唯一一份,而且只需要處理老年代到新生代的引用,反過(guò)來(lái)則不需要,由于新生代的對(duì)象具有朝生夕滅的不穩(wěn)定性,引用變化頻繁,能省下這個(gè)區(qū)域的維護(hù)開(kāi)銷是很劃算的
    • 在執(zhí)行負(fù)載的角度上,同樣由于兩個(gè)收集器各自的細(xì)節(jié)實(shí)現(xiàn)特點(diǎn)導(dǎo)致了用戶程序運(yùn)行時(shí)的負(fù)載會(huì)有不同,譬如都使用到寫屏障,CMS用寫后屏障來(lái)更新維護(hù)卡表;而G1除了使用寫后屏障來(lái)進(jìn)行同樣的(由于G1的卡表結(jié)構(gòu)復(fù)雜,其實(shí)是更煩瑣的)卡表維護(hù)操作外,為了實(shí)現(xiàn)原始快照搜索(SATB)算法,還需要使用寫前屏障來(lái)跟蹤并發(fā)時(shí)的指針變化情況。相比起增量更新算法,原始快照搜索能夠減少并發(fā)標(biāo)記和重新標(biāo)記階段的消耗,避免CMS那樣在最終標(biāo)記階段停頓時(shí)間過(guò)長(zhǎng)的缺點(diǎn),但是在用戶程序運(yùn)行過(guò)程中確實(shí)會(huì)產(chǎn)生由跟蹤引用變化帶來(lái)的額外負(fù)擔(dān)。由于G1對(duì)寫屏障的復(fù)雜操作要比CMS消耗更多的運(yùn)算資源,所以CMS的寫屏障實(shí)現(xiàn)是直接的同步操作,而G1就不得不將其實(shí)現(xiàn)為類似于消息隊(duì)列的結(jié)構(gòu),把寫前屏障和寫后屏障中要做的事情都放到隊(duì)列里,然后再異步處理

    閱讀參考

    • 7款經(jīng)典的垃圾收集器以及它們之間的組合關(guān)系、如何查看默認(rèn)的垃圾回收器(包括垃圾收集器之間組合關(guān)系 和 查看默認(rèn)的垃圾收集器)
    • Serial與Serial Old收集器、ParNew收集器、Paralell Scavenge與Parallel Old收集器、CMS收集器 其中有“如何選擇Serial GC和Parallel GC和CMS GC?
    • G1垃圾收集器、優(yōu)勢(shì)與缺點(diǎn)、參數(shù)設(shè)置、使用場(chǎng)景 其中有"G1使用場(chǎng)景"

    垃圾收集器總結(jié)

    新生代收集器/老年代收集器

    新生代收集器:Serial、ParNew、Parallel Scavenge
    老年代收集器:Serial Old、Parallel Old、CMS
    整堆收集器:G1

    吞吐量?jī)?yōu)先、停頓時(shí)間優(yōu)先

    吞吐量?jī)?yōu)先:Parallel Scavenge收集器、Parallel Old收集器
    停頓時(shí)間優(yōu)先:CMS(Concurrent Mark-Sweep)收集器

    串行、并行、并發(fā)(重要!!!)

    串行:Serial、Serial Old
    并行:ParNew、Parallel Scavenge、Parallel Old
    并發(fā):CMS、G1

    回收算法

    新生代的垃圾收集器都是使用的復(fù)制算法
    復(fù)制算法:Serial、ParNew、Parallel Scavenge、G1

    老年代的垃圾收集器使用的算法是 標(biāo)記-整理 和 標(biāo)記-清除算法(只有CMS)
    標(biāo)記-清除:CMS
    標(biāo)記-整理:Serial Old、Parallel Old、G1

    G1既回收新生代又回收老年代,所以使用的是復(fù)制算法 和 標(biāo)記-整理算法

    JDK默認(rèn)垃圾收集器(重要!!!)

    可以通過(guò)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

    表格

    收集器串行、并行or并發(fā)新生代/老年代算法目標(biāo)適用場(chǎng)景
    Serial串行新生代復(fù)制算法響應(yīng)速度優(yōu)先單CPU環(huán)境下的Client模式
    Serial Old串行老年代標(biāo)記-整理算法響應(yīng)速度優(yōu)先單CPU環(huán)境下的Client模式、CMS的后備預(yù)案
    ParNew并行新生代復(fù)制算法響應(yīng)速度優(yōu)先多CPU環(huán)境時(shí)在Server模式下與CMS配合
    Parallel Scavenge并行新生代復(fù)制算法吞吐量?jī)?yōu)先在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)
    Parallel Old并行老年代標(biāo)記-整理算法吞吐量?jī)?yōu)先在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)
    CMS并行、并發(fā)老年代標(biāo)記-清除算法響應(yīng)速度優(yōu)先集中在互聯(lián)網(wǎng)站或B/S系統(tǒng)服務(wù)端上的Java應(yīng)用
    G1并發(fā)、并發(fā)新生代 + 老年代標(biāo)記-整理 + 標(biāo)記-清除算法響應(yīng)速度優(yōu)先面向服務(wù)端應(yīng)用,替換CMS

    垃圾收集器參數(shù)總結(jié)

    如何選擇垃圾收集器(重要!!!)

    如果是數(shù)據(jù)分析、科學(xué)計(jì)算類的任務(wù),目標(biāo)是能盡快算出結(jié)果,那吞吐量就是主要關(guān)注點(diǎn)
    如果是SLA應(yīng)用,那停頓時(shí)間直接影響服務(wù)質(zhì)量,嚴(yán)重的甚至?xí)?dǎo)致事務(wù)超時(shí),這樣延遲就是主要關(guān)注點(diǎn)
    如果是客戶端應(yīng)用或者嵌入式應(yīng)用,那垃圾收集的內(nèi)存占用則是不可忽視的

    垃圾回收器總結(jié)、怎么選擇垃圾收集器

    回收方法區(qū)(重要!!!)

    • 主要回收兩部分內(nèi)容:廢棄常量 + 無(wú)用的類
    • 判斷一個(gè)常量 是否是 廢棄常量,只需要看 是否有任何對(duì)象 引用 當(dāng)前這個(gè)常量
    • 判斷一個(gè)類 是否是 無(wú)用的類,條件很苛刻:
    • 該類的 所有實(shí)例 都已經(jīng)被回收,Java堆中 不存在 該類及其任何派生子類 的 實(shí)例
    • 加載該類的 ClassLoader類加載器 已經(jīng)被回收,這個(gè)條件 除非是 經(jīng)過(guò)精心設(shè)計(jì)的 可替換類加載器 的 場(chǎng)景,如OSGi、JSP的重加載等,否則通常是很難達(dá)成的
    • 該類 對(duì)應(yīng)的 java.lang.Class對(duì)象 沒(méi)有在任何地方被引用,無(wú)法在任何地方 通過(guò)反射 訪問(wèn)該類的方法

    方法區(qū)相關(guān)參數(shù)

    • -Xnoclassgc,關(guān)閉虛擬機(jī)對(duì)class的垃圾回收功能
    • -verbose:class,查看在程序運(yùn)行的時(shí)候有多少類被加載
    • -XX:+TraceClassLoading,動(dòng)態(tài)追蹤類加載
    • -XX:+TraceClassUnLoading,動(dòng)態(tài)追蹤類卸載

    閱讀參考:方法區(qū)的垃圾回收

    GC日志

    GC日志相關(guān)參數(shù)

    • -XX:+PrintGC、-Xlog:gc 輸出GC日志
    • -XX:+PrintGCDetails、-Xlog:gc*, 輸出GC詳細(xì)日志
    • -XX:+PrintGCTimeStamps 輸出GC的時(shí)間戳,基準(zhǔn)時(shí)間形式
    • -XX:+PrintGCDateStamps 輸出GC的時(shí)間戳,日期形式
    • -XX:+PrintHeapAtGC、-Xlog:gc+heap=debug 在GC進(jìn)行的前后打印堆日志
    • -Xloggc:../logs/gc.log 日志文件的輸出路徑
    • -XX:+PrintGCApplicationConcurrentTime以及-XX:+PrintGCApplicationStoppedTime、-Xlog:safepoint 查看GC過(guò)程中用戶線程并發(fā)時(shí)間以及停頓的時(shí)間
    • -XX:+PrintAdaptive-SizePolicy、-Xlog:gc+ergo*=trace 查看收集器Ergonomics機(jī)制(自動(dòng)設(shè)置堆空間各分代區(qū)域大小、收集目標(biāo)等內(nèi)容,從Parallel收集器開(kāi)始支持)自動(dòng)調(diào)節(jié)的相關(guān)信息
    • -XX:+PrintTenuring-Distribution、-Xlog:gc+age=trace查看熬過(guò)收集后剩余對(duì)象的年齡分布信息

    GC日志內(nèi)容

    • [GC/[Full GC,說(shuō)明垃圾收集的停頓類型
    • [DefNew、[Tenured、[Prem,代表GC發(fā)生的區(qū)域
    • Serial在新生代收集器的名字是Default New Generation,因此顯示的是DefNew
    • 使用ParNew收集器在新生代的名字會(huì)變?yōu)镻arNew
    • 使用Parallel Scavenge收集器在新生代的名字是PSYoungGen
    • 老年代收集 和 新生代收集的道理一樣,名字也是收集器決定的
    • G1收集器 會(huì)顯式為 garbage-first heap
    • Metaspace 元數(shù)據(jù)區(qū)
    • 方括號(hào)內(nèi)部:GC前 該內(nèi)存區(qū)域 已使用容量 -> GC后 該內(nèi)存區(qū)域 已使用區(qū)域(該區(qū)域內(nèi)存總量)
    • 方括號(hào)外部:GC前 java堆 已使用容量 -> GC后 java堆 已使用容量(java堆總?cè)萘?
    • 最后的數(shù)字表明 該內(nèi)存區(qū)域 GC所占用的時(shí)間,單位是s
    • user,垃圾收集器 花費(fèi)的 所有cpu時(shí)間
    • sys,花費(fèi)在 等待系統(tǒng)調(diào)用 或 系統(tǒng)事件的時(shí)間
    • real,真正GC從開(kāi)始到結(jié)束的時(shí)間

    閱讀參考:常用的顯示GC日志的參數(shù)、GC日志分析、日志分析工具的使用

    總結(jié)

    以上是生活随笔為你收集整理的JVM面试(四)-垃圾回收、垃圾收集器、GC日志的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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