日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

面试必会系列 - 1.6 Java 垃圾回收机制

發(fā)布時(shí)間:2024/2/28 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 面试必会系列 - 1.6 Java 垃圾回收机制 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文已收錄至 Github(MD-Notes),若博客中有圖片打不開,可以來我的 Github 倉庫:https://github.com/HanquanHq/MD-Notes,涵蓋了互聯(lián)網(wǎng)大廠面試必問的知識(shí)點(diǎn),講解透徹,長(zhǎng)期更新中,歡迎一起學(xué)習(xí)探討 ~ 另外:
面試必會(huì)系列專欄:https://blog.csdn.net/sinat_42483341/category_10300357.html
操作系統(tǒng)系列專欄:https://blog.csdn.net/sinat_42483341/category_10519484.html


垃圾回收機(jī)制

垃圾:沒有引用指向的對(duì)象

JVM 堆分代模型

JVM 內(nèi)存模型和具體的垃圾回收器有關(guān),面試問的時(shí)候,你應(yīng)該說明是哪一種回收器!

新生代(堆空間)
  • 分為 1 個(gè) 伊甸區(qū),2 個(gè) survivor 區(qū)
  • 存活對(duì)象少,使用 拷貝算法
老年代(堆空間)
  • 存活對(duì)象多,使用 標(biāo)記壓縮算法 / 標(biāo)記清除算法
永久代(方法區(qū) MethodArea)(堆之外空間)

存的是class的元信息,代碼的編譯信息等待等等

  • 1.8 之前:Perm Generation,固定設(shè)置大小
  • 1.8 之后:MetaSpace,默認(rèn)受限于物理內(nèi)存,可以設(shè)置
  • 字符串常量1.7在Perm Generation,1.8在堆內(nèi)存

除了 Epsilon,ZGC,Shenandoah 之外的垃圾回收器,都是 邏輯分代 模型

G1是 邏輯分代,物理不分代(物理分代就是內(nèi)存里確實(shí)有這樣一塊空間)

除此之外,其余的不僅邏輯分代,而且物理分代。

對(duì)象何時(shí)進(jìn)入老年代?

對(duì)象頭 MarkWord 中存儲(chǔ)分代年齡的只有 4 bit,所以最大是15次。

PS 默認(rèn)是15,CMS 默認(rèn)是 6。

對(duì)象分配過程?

  • new 一個(gè)對(duì)象,首先嘗試在 上分配。如果能分配到棧上,則分配到棧上。

    為什么?因?yàn)橐坏┓椒◤棾?#xff0c;整個(gè)生命周期就結(jié)束,不需要垃圾回收,提高了效率。

    什么樣的對(duì)象能在棧上分配?滿足可以進(jìn)行 逃逸分析標(biāo)量替換 的對(duì)象,可以分配到棧上

    什么是逃逸分析?如果判斷一段代碼中堆上的所有數(shù)據(jù)都只被一個(gè)線程訪問,就可以當(dāng)作棧上的數(shù)據(jù)對(duì)待,認(rèn)為它們是線程私有的,無須同步。例如,一個(gè)對(duì)象只出現(xiàn)在 for 循環(huán)內(nèi)部,沒有外部的引用。

    什么是標(biāo)量替換?如果你這個(gè)對(duì)象可以被分解為基礎(chǔ)數(shù)據(jù)類型來替換,比如一個(gè)對(duì)象 T 有兩個(gè) int 類型。類似于 C 的結(jié)構(gòu)體。

  • 如果棧上分配不下,判斷是否 大對(duì)象(多大?有參數(shù)可以設(shè)置)

    • 如果是大對(duì)象,直接進(jìn)入 old 區(qū)
    • 如果不是大對(duì)象,放入 ThreadLocalAllocationBuffer(TLAB) 線程本地分配緩沖區(qū),它其實(shí)是在 eden 區(qū)分配給每個(gè)線程的一小塊空間,線程把對(duì)象 new 在自己兜里,兜里滿了再去搶公共的空間,避免 new 對(duì)象的時(shí)候不同線程對(duì)空間的征用。
  • 進(jìn)行 GC 清除,在分代模型 GC 下:

    • 如果 是垃圾,則對(duì)象被清除
    • 如果 不是垃圾,進(jìn)入 survivor 區(qū),或在 survivor 區(qū)之間復(fù)制
    • 如果 survivor 區(qū)年齡到達(dá) 15,進(jìn)入 old 區(qū)
  • 動(dòng)態(tài)年齡,分配擔(dān)保:了解即可

    • 動(dòng)態(tài)對(duì)象年齡判定:為了適應(yīng)不同內(nèi)存狀況,虛擬機(jī)不要求對(duì)象年齡達(dá)到閾值才能晉升老年代,如果在 Survivor 中相同年齡所有對(duì)象大小的總和大于 Survivor 的一半,年齡不小于該年齡的對(duì)象就可以直接進(jìn)入老年代。

    • 空間分配擔(dān)保:MinorGC 前虛擬機(jī)必須檢查老年代最大可用連續(xù)空間是否大于新生代對(duì)象總空間,如果滿足則說明這次 Minor GC 確定安全。

      如果不滿足,虛擬機(jī)會(huì)查看 -XX:HandlePromotionFailure 參數(shù)是否允許擔(dān)保失敗,如果允許會(huì)繼續(xù)檢查老年代最大可用連續(xù)空間是否大于歷次晉升老年代對(duì)象的平均大小,如果滿足將冒險(xiǎn)嘗試一次 Minor GC,否則改成一次 FullGC。

      冒險(xiǎn)是因?yàn)樾律褂脧?fù)制算法,為了內(nèi)存利用率只使用一個(gè) Survivor,大量對(duì)象在 Minor GC 后仍然存活時(shí),需要老年代進(jìn)行分配擔(dān)保,接收 Survivor 無法容納的對(duì)象。

    為什么 hotspot 不使用 C++ 對(duì)象來代表 java 對(duì)象?

    為什么不為每個(gè)Java類生成一個(gè)C++類與之對(duì)應(yīng)?

    因?yàn)?C++ 對(duì)象里面有一個(gè) virtual table 指針,而HotSopt JVM的設(shè)計(jì)者不想讓每個(gè)對(duì)象中都含有一個(gè)vtable(虛函數(shù)表),而是設(shè)計(jì)了一個(gè) OOP-Klass Model,把對(duì)象模型拆成 klass 和 oop

    • OOP 指的是 Ordinary Object Pointer (普通對(duì)象指針),它用來表示對(duì)象的實(shí)例信息,看起來像個(gè)指針實(shí)際上是藏在指針里的對(duì)象。OOP 中不含有任何虛函數(shù)。

    • Klass 簡(jiǎn)單的說是Java類在HotSpot中的 C++ 對(duì)等體,用來描述 Java 類。Klass 含有虛函數(shù)表,可以進(jìn)行method dispatch,Klass主要有兩個(gè)功能:

      • 實(shí)現(xiàn)語言層面的Java類
      • 實(shí)現(xiàn)Java對(duì)象的分發(fā)功能

    Class 對(duì)象是在堆還是在方法區(qū)?

    在程序運(yùn)行期間,Java 運(yùn)行時(shí)系統(tǒng)為所有對(duì)象維護(hù)一個(gè)運(yùn)行時(shí)類型標(biāo)識(shí),這個(gè)信息會(huì)跟蹤每個(gè)對(duì)象所屬的類,虛擬機(jī)利用運(yùn)行時(shí)類型信息選擇要執(zhí)行的正確方法,保存這些信息的類就是 Class,這是一個(gè)泛型類。

    運(yùn)行時(shí)常量池的作用是什么?

    運(yùn)行時(shí)常量池是 方法區(qū)(hotspot 的實(shí)現(xiàn)為元數(shù)據(jù)區(qū)) 的一部分

    Class 文件 中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是 常量池表,用于存放編譯器生成的各種 字面量符號(hào)引用,這部分內(nèi)容在類加載后,被存放到 運(yùn)行時(shí)常量池。一般除了保存 Class 文件中描述的 符號(hào)引用 外,還會(huì)把符號(hào)引用翻譯的 直接引用 也存儲(chǔ)在運(yùn)行時(shí)常量池。

    運(yùn)行時(shí)常量池相對(duì)于 Class 文件常量池的一個(gè)重要特征是動(dòng)態(tài)性,Java 不要求常量只有編譯期才能產(chǎn)生,運(yùn)行期間也可以將新的常量放入池中,這種特性利用較多的是 String 的 intern 方法。

    運(yùn)行時(shí)常量池是方法區(qū)的一部分,受到方法區(qū)內(nèi)存的限制,常量池?zé)o法申請(qǐng)到內(nèi)存時(shí)拋出 OutOfMemoryError。

    如何判斷一個(gè)常量是廢棄常量?

    我們需要對(duì)運(yùn)行時(shí)常量池的廢棄常量進(jìn)行回收。那么,如何判斷一個(gè)常量是廢棄常量呢?

    沒有棧引用指向這個(gè)常量,這個(gè)常量就是廢棄的。

    假如在常量池中存在字符串 “abc”,如果當(dāng)前沒有任何 String 對(duì)象引用該字符串常量的話,就說明常量 “abc” 就是廢棄常量,如果這時(shí)發(fā)生內(nèi)存回收的話而且有必要的話,“abc” 就會(huì)被系統(tǒng)清理出常量池。

    GC的分類

    MinorGC / YoungGC:年輕代空間耗盡時(shí)觸發(fā)

    MajorGC / FullGC:老年代無法分配空間時(shí)觸發(fā),新生代、老年代同時(shí)進(jìn)行回收

    “找到”垃圾的算法?

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

    在對(duì)象中添加一個(gè)引用計(jì)數(shù)器,如果被引用計(jì)數(shù)器加 1,引用失效時(shí)計(jì)數(shù)器減 1,如果計(jì)數(shù)器為 0 則被標(biāo)記為垃圾。原理簡(jiǎn)單,效率高,在 Java 中很少使用,因?qū)ο箝g循環(huán)引用的問題會(huì)導(dǎo)致計(jì)數(shù)器無法清零。

    2、根可達(dá)算法(RootSearching)

    通過一系列 GC Roots 對(duì)象 作為起始點(diǎn),開始向下搜索,若一個(gè)對(duì)象沒有任何引用鏈相連,則不可達(dá)。

    GC Roots 對(duì)象 包括:

    • JVM stack - JVM 棧
    • native method stack - 本地方法棧
    • run-time constant pool - 運(yùn)行時(shí)常量池
    • static references in method area - 方法區(qū)靜態(tài)方法
    • Clazz - 類

    “清除”垃圾的算法?

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

    • 需要兩遍掃描:第一遍標(biāo)記,第二遍清除
    • 產(chǎn)生碎片,內(nèi)存空間不連續(xù),大對(duì)象分配不到內(nèi)存觸發(fā) FGC
    • 適合 存活對(duì)象較多 的情況,不適合Eden區(qū)
    2、Copying 拷貝算法

    HotSpot 把新生代劃分為一塊較大的 Eden 和兩塊較小的 Survivor。垃圾收集時(shí)將 Eden 和 Survivor 中仍然存活的對(duì)象一次性復(fù)制到另一塊 Survivor 上,然后直接清理掉 Eden 和已用過的那塊 Survivor。

    • 只掃描一次,效率高
    • 不產(chǎn)生碎片
    • 空間折半浪費(fèi)
    • 移動(dòng)、復(fù)制對(duì)象時(shí),需要調(diào)整對(duì)象引用
    • 適合 存活對(duì)象比較少 的情況,老年代一般使用此算法
    3、Mark-Compact 標(biāo)記壓縮算法

    • 需要掃描兩次,需要移動(dòng)對(duì)象,效率偏低,開銷大,停頓時(shí)間長(zhǎng)
    • 不產(chǎn)生碎片
    • 不會(huì)產(chǎn)生內(nèi)存減半
    • 老年代使用
    4、分代收集算法

    現(xiàn)在的商用虛擬機(jī)的垃圾收集器,基本都采用"分代收集"算法,根據(jù)對(duì)象存活周期的不同將內(nèi)存分為幾塊。一般將java堆分為新生代、老年代,這樣我們可以根據(jù)各個(gè)年代的特點(diǎn),選擇合適的垃圾收集算法。

    新生代 每次收集都有大量對(duì)象死去,所以選擇 復(fù)制算法,只要付出少量對(duì)象復(fù)制成本,就可以完成垃圾收集。IBM 做過統(tǒng)計(jì),工業(yè)上一般來講,一次回收會(huì)回收掉 90% 的對(duì)象。

    老年代 對(duì)象存活幾率比高,且無額外的空間對(duì)它進(jìn)行分配擔(dān)保,就必須選擇 標(biāo)記-清除 或者 標(biāo)記-壓縮 行垃圾收集。

    常見的垃圾回收器

    隨著硬件內(nèi)存越來越大,原有的回收器需要回收的時(shí)間變得越來越長(zhǎng),于是發(fā)展出了各種各樣的垃圾回收器。

    常見組合:

    • Serial + Serial Old
    • ParNew + CMS
    • Parallel Scavenge + Parallel Old(PS + PO),1.8 默認(rèn)
    • G1

    1、Serial + Serial Old

    • STW
    • 單線程
    • Serial 年輕代 復(fù)制算法,Serial Old 老年代 標(biāo)記-壓縮算法
    • 與其他收集器的單線程相比,簡(jiǎn)單高效,對(duì)于運(yùn)行在 client 模式下的虛擬機(jī)是個(gè)不錯(cuò)的選擇

    2、Parallel Scavenge + Parallel Old(jdk 8 默認(rèn))

    10G內(nèi)存的話,回收一次要十幾秒

    • STW
    • 多個(gè)GC線程并行回收
    • 年輕代 復(fù)制算法,老年代 標(biāo)記-壓縮算法
    • 線程數(shù)不可能被無限增多,CPU會(huì)將資源耗費(fèi)在線程切換上

    3、ParNew + CMS

    java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC com.mashibing.jvm.gc.T15_FullGC_Problem01
    ParNew 年輕代
    • STW
    • 多個(gè)GC線程
    • ParNew 與 Parallel Scavenge 相似,只不過 ParNew 有可以與 CMS 結(jié)合的同步機(jī)制。除了 Serial 外,只有它能與 CMS 配合。
    CMS 老年代
    • 獲取最短回收停頓時(shí)間 為目標(biāo),并發(fā)回收,用戶線程和 GC 線程同時(shí)進(jìn)行,暫停時(shí)間短

    • 基于 標(biāo)記-清除 算法

    • 分為 四個(gè)階段

      • 初始標(biāo)記(STW):暫停所有其他線程,并記錄直接與 root 相連的對(duì)象。初始垃圾并不多,因此很快。
      • 并發(fā)標(biāo)記:垃圾回收線程和用戶線程同時(shí)執(zhí)行。一邊產(chǎn)生垃圾,一邊標(biāo)記(最耗時(shí)的階段并發(fā)執(zhí)行)。
      • 重新標(biāo)記(STW):對(duì)并發(fā)標(biāo)記的過程中新產(chǎn)生的垃圾進(jìn)行重新標(biāo)記 / 取消標(biāo)記,修正錯(cuò)標(biāo)。由于 CMS 的并發(fā)標(biāo)記存在并發(fā)漏標(biāo)的問題,所以 CMS 的 remark 階段必須從頭掃描一遍,耗時(shí)很長(zhǎng),也是為什么 JDK 默認(rèn)并沒有使用 CMS 的 原因。
      • 并發(fā)清理:開啟用戶線程,同時(shí) GC 線程開始對(duì)未標(biāo)記的區(qū)域做清掃。清理的過程也會(huì)產(chǎn)生新的“浮動(dòng)垃圾”,需要等下一次CMS重新運(yùn)行的時(shí)候再次清理,影響不大。
    • CMS 存在的 問題

      • Memory Fragmentation 內(nèi)存碎片 問題

        • 標(biāo)記清除會(huì)產(chǎn)生碎片化,如果老年代不能再分配位置,CMS會(huì)讓 Serial Old 來清理,效率很低。

        • 解決方案:

          -XX:+UseCMSCompactAtFullCollection 在 FGC 時(shí)進(jìn)行壓縮

          -XX:CMSFullGCsBeforeCompaction 多少次 FGC 之后進(jìn)行壓縮,默認(rèn)是 0

      • Floating Garbage 浮動(dòng)垃圾 問題

        • 如果老年代滿了,浮動(dòng)垃圾還沒有清理完,會(huì)讓 Serial Old 清理。

        • 解決方案:

          降低觸發(fā) CMS 的閾值,保持老年代有足夠的空間

          -XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后開始CMS收集。實(shí)際回收的時(shí)候它是一個(gè)近似值,可能沒達(dá)到這個(gè)值就已經(jīng)觸發(fā)了。默認(rèn)是68%,有人說是 92%,好像有個(gè)計(jì)算公式,沒有去深究。如果頻繁發(fā)生SerialOld卡頓,應(yīng)該把它調(diào)小,但是調(diào)小的缺點(diǎn)是頻繁CMS回收。

          如何查看這個(gè)默認(rèn)值?

          java -XX:+PrintFlagsFinal -version | grep CMSInitiatingOccupancyFraction

    4、G1 回收器

    開創(chuàng)了收集器面向局部收集的設(shè)計(jì)思路和基于 Region 的內(nèi)存布局,主要面向服務(wù)端,最初設(shè)計(jì)目標(biāo)是替換 CMS

    如果你生產(chǎn)是 1.8 的 jdk,推薦使用 G1 回收器。啟動(dòng)方式:

    java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+Use G1GC com.mashibing.jvm.gc.T15_FullGC_Problem01

    G1的特點(diǎn):適用于需要特別快的響應(yīng)時(shí)間的場(chǎng)景(不需要很高吞吐量)。可由用戶指定期望停頓時(shí)間是 G1 的一個(gè)強(qiáng)大功能,但該值不能設(shè)得太低,一般設(shè)置為100~300 ms

    G1的新老年代的比例是動(dòng)態(tài)的,默認(rèn)年輕代占用 5%-60%,一般不用手工指定,也不要手工指定。因?yàn)檫@是G1預(yù)測(cè)停頓時(shí)間的基準(zhǔn)。它會(huì)根據(jù)上次回收的時(shí)間,進(jìn)行新老年代的比例的動(dòng)態(tài)調(diào)整。

    G1 的一些概念:
    1、card table 卡表
    • 基于 card table,將堆空間劃分為一系列2^n大小的 card page
    • card table 用 bitmap 來實(shí)現(xiàn),用于標(biāo)記卡頁的狀態(tài),每個(gè) card table 項(xiàng)對(duì)應(yīng)一個(gè) card page
    • 當(dāng)對(duì)一個(gè)對(duì)象引用進(jìn)行寫操作時(shí)(對(duì)象引用改變),寫屏障邏輯會(huì)標(biāo)記對(duì)象所在的 card page 為 dirty
    2、Cset(collection set)

    Cset 是一組可以被回收的分區(qū)的集合。它里面記錄了有哪些對(duì)象需要被回收。

    3、Rset(remembered set)

    Rset 中有一個(gè) Hash 表,里面記錄了其它 region 中的對(duì)象到本 region 的引用。

    Rset 的價(jià)值在于,它使得垃圾收集器不需要掃描整個(gè)堆,去找誰引用了當(dāng)前分區(qū)中的對(duì)象,只需要掃描 Rset 即可。

    Rset 會(huì)不會(huì)影響賦值的效率?會(huì)!由于Rset的存在,那么每次給對(duì)象復(fù)制引用的時(shí)候,需要在Rset中做一些額外的記錄,比如說記錄有哪些引用指向了我的對(duì)象等等,這些操作在GC中被稱為寫屏障。此處不同于內(nèi)存屏障,是GC專有的寫屏障。NO Silver Bullet!只有特定條件下特定的解決方案,沒有通用的解決方案。

    G1 的回收過程:
    • 把內(nèi)存空間分為一塊一塊的 region,把內(nèi)存化整為零。

    • G1 收集器在后臺(tái)維護(hù)了一個(gè)優(yōu)先列表,當(dāng)需要進(jìn)行垃圾回收時(shí),會(huì)優(yōu)先回收存活對(duì)象最少的 region,也就是垃圾最多的 region,這就是“Garbage-First 垃圾優(yōu)先”。每一個(gè) region 都有自己的邏輯分代

      • old
      • suvivor
      • eden
      • humongous 存放巨型對(duì)象(跨越多個(gè)region的對(duì)象)
    • G1 的 GC 分為三種,不同種類的 GC 可能會(huì)同時(shí)進(jìn)行。比如 YGC 和 MixedGC 的 initial mark 同時(shí)進(jìn)行

      • YGC

      • MixedGC:比如YGC已經(jīng)回收不過來的,堆內(nèi)存空間超過了45%,默認(rèn)就啟動(dòng)了MixedGC。

        • MixedGC 發(fā)生的閾值可以自行設(shè)定

        • MixedGC 相當(dāng)于一套完整的 CMS

          • 初始標(biāo)記 需要STW

            標(biāo)記 GC Roots 能直接關(guān)聯(lián)到的對(duì)象,讓下一階段用戶線程并發(fā)運(yùn)行時(shí)能正確地在可用 Region 中分配新對(duì)象。需要 STW 但耗時(shí)很短,在 Minor GC 時(shí)同步完成

          • 并發(fā)標(biāo)記 不需要STW

            從 GC Roots 開始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,遞歸掃描整個(gè)堆的對(duì)象圖。耗時(shí)長(zhǎng)但可與用戶線程并發(fā),掃描完成后要重新處理 SATB 記錄的在并發(fā)時(shí)有變動(dòng)的對(duì)象,三色標(biāo)記 算法

          • 最終標(biāo)記 需要STW(重新標(biāo)記)

            對(duì)用戶線程做短暫暫停,處理并發(fā)階段結(jié)束后仍遺留下來的少量 SATB 記錄

          • 篩選回收 需要STW(并行)

            對(duì)各 Region 的回收價(jià)值排序,根據(jù)用戶期望停頓時(shí)間制定回收計(jì)劃。必須暫停用戶線程,由多條收集線程并行完成

      • FullGC:G1 也是有 FGC 的,對(duì)象分配不下的時(shí)候,就會(huì)產(chǎn)生 FGC。我們說 G1 和 CMS 調(diào)優(yōu)目標(biāo)之一就是盡量不要有 FGC,但這并不容易達(dá)到。因此有了面試題:如果G1產(chǎn)生FGC,你應(yīng)該做什么?

      • 擴(kuò)內(nèi)存
      • 提高CPU性能(回收的快,業(yè)務(wù)邏輯產(chǎn)生對(duì)象的速度固定,垃圾回收越快,內(nèi)存空間越大)
      • 降低MixedGC觸發(fā)的閾值,讓MixedGC提早發(fā)生(默認(rèn)是45%)
      并發(fā)標(biāo)記算法 - 三色標(biāo)記算法

    • CMS 和 G1 用到的都是 三色標(biāo)記 算法,“三色”只是邏輯概念,并不是真的顏色。

      • 白色:未被標(biāo)記的對(duì)象,也就是還沒有遍歷到的節(jié)點(diǎn)
      • 灰色:自身標(biāo)記完成,沒來得及標(biāo)記成員變量
      • 黑色:自身和成員變量均標(biāo)記完成
    • 漏標(biāo):本來是 live object,但是由于沒有遍歷到,被當(dāng)成 garbage 回收掉了。例如,在 remark 的過程中,B 原來指向 D 的線消失了,但是 A 又指向了 D。如果不對(duì) A 進(jìn)行重新掃描,則會(huì)漏標(biāo),導(dǎo)致D被當(dāng)做垃圾回收掉。

    • 漏標(biāo)解決方案:

      • Incremental update,CMS 方案:增量更新,關(guān)注引用的 增加,把黑色的 A 重新標(biāo)記為灰色,下次重新掃描它。

      • SATB(snapshot at the begining),G1方案:關(guān)注引用的 刪除。當(dāng) B->D 消失時(shí),要把這個(gè)引用推到GC的堆棧,保證D還能被GC掃描到。下次掃描時(shí)會(huì)拿到這個(gè)引用,由于有Rset的存在,不需要掃描整個(gè)堆去查找指向白色的引用,效率比較高。

        SATB配合Rset,渾然天成。

    5、ZGC

    美團(tuán)技術(shù)團(tuán)隊(duì):新一代垃圾回收器ZGC的探索與實(shí)踐

    ZGC 用了顏色指針

    一些拓展知識(shí)
    • 阿里的多租戶 JVM:把一個(gè) JVM 分為還幾個(gè)小塊,分給租戶用
    • 專門針對(duì) web application 的,session base 的 JVM。請(qǐng)求來訪問的時(shí)候產(chǎn)生的垃圾,在請(qǐng)求結(jié)束之后被回收

    JVM 常用命令參數(shù)

    HotSpot參數(shù)分類

    標(biāo)準(zhǔn): - 開頭,所有的HotSpot都支持 非標(biāo)準(zhǔn):-X 開頭,特定版本HotSpot支持特定命令 不穩(wěn)定:-XX 開頭,下個(gè)版本可能取消

    調(diào)優(yōu)前的基礎(chǔ)概念:

    所謂調(diào)優(yōu),首先確定:是吞吐量?jī)?yōu)先,還是響應(yīng)時(shí)間優(yōu)先?還是在一定的響應(yīng)時(shí)間下,要求達(dá)到多大的吞吐量?

    • 吞吐量:用戶代碼時(shí)間 /(用戶代碼執(zhí)行時(shí)間 + 垃圾回收時(shí)間)

      • 科學(xué)計(jì)算、數(shù)據(jù)挖掘,一般吞吐量?jī)?yōu)先
      • PS + PO
    • 響應(yīng)時(shí)間:STW越短,響應(yīng)時(shí)間越好

      • 網(wǎng)站、GUI、API,一般響應(yīng)時(shí)間優(yōu)先
      • 1.8 G1

    并發(fā):淘寶雙11并發(fā)歷年最高54萬,據(jù)說12306并發(fā)比淘寶更高,號(hào)稱上百萬

    • TPS
    • QPS,Query Per Second

    什么是調(diào)優(yōu)?

    1、如何根據(jù)需求進(jìn)行 JVM 規(guī)劃和預(yù)調(diào)優(yōu)?

    有人要問你,你應(yīng)該選用多大的內(nèi)存?什么樣的垃圾回收器組合?你怎么回答?

    • 要有實(shí)際的業(yè)務(wù)場(chǎng)景,才能討論調(diào)優(yōu)
    • 要有監(jiān)控,能夠通過壓力測(cè)試看到結(jié)果

    步驟:

    (1)熟悉業(yè)務(wù)場(chǎng)景,選擇合適的垃圾回收器。是追求吞吐量,還是追求響應(yīng)時(shí)間?

    (2)計(jì)算內(nèi)存需求。沒有一定之規(guī),是經(jīng)驗(yàn)值。 1.5G -> 16G,突然卡頓了,為啥?

    (3)選定CPU。預(yù)算能買到的,當(dāng)然是越高越好,CPU多核,可以多線程運(yùn)行呀!

    (4)設(shè)定年代大小、升級(jí)年齡

    (5)設(shè)定 日志參數(shù)

    這個(gè)是 Java 虛擬機(jī)的參數(shù),也可以在Tomcat里面配置,貌似是在叫 catalina options 里面指定java日志的參數(shù)

    -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause HelloGC

    5個(gè)日志文件循環(huán)產(chǎn)生,生產(chǎn)中的日志一般是這么設(shè)置,%t是生成時(shí)間的意思。

    (6)觀察日志情況

    案例1:垂直電商,最高每日百萬訂單,處理訂單系統(tǒng)需要什么樣的服務(wù)器配置?

    這個(gè)問題比較業(yè)余,因?yàn)楹芏嗖煌姆?wù)器配置都能支撐(1.5G 16G 都有可能啊)

    我們做一個(gè)假設(shè)吧,1小時(shí)360000個(gè)訂單。在集中時(shí)間段, 100個(gè)訂單/秒,(找一小時(shí)內(nèi)的高峰期,可能是1000訂單/秒)。我們就要找到這個(gè)最高峰的時(shí)間,保證你的架構(gòu)能夠承接的住。

    大多數(shù)情況下,是靠經(jīng)驗(yàn)值,然后做壓測(cè)。

    如果非要計(jì)算的話,你預(yù)估一下,一個(gè)訂單對(duì)象產(chǎn)生需要多少內(nèi)存?512K * 1000 = 500M

    專業(yè)一點(diǎn)的問法:要求響應(yīng)時(shí)間在多少時(shí)間的情況下,比如100ms,我們?nèi)ヌ粢粋€(gè)市面上性價(jià)比比較高的服務(wù)器,做壓測(cè)去測(cè)試,再不行加內(nèi)存,再不行,就上云服務(wù)器…
    這樣說就OK了

    案例2:12306遭遇春節(jié)大規(guī)模搶票應(yīng)該如何支撐?(架構(gòu)上的一個(gè)設(shè)計(jì),與調(diào)優(yōu)關(guān)系不大)

    12306應(yīng)該是中國并發(fā)量最大的秒殺網(wǎng)站:號(hào)稱并發(fā)量最高100W

    架構(gòu)模型:CDN -> LVS -> NGINX -> 業(yè)務(wù)系統(tǒng) -> 100臺(tái)機(jī)器,每臺(tái)機(jī)器1W并發(fā)(單機(jī)10K問題),目前這個(gè)問題主要用Redis解決

    業(yè)務(wù)流程:普通電商訂單 -> 下單 -> 訂單系統(tǒng)(IO)減庫存 -> 生成訂單,等待用戶付款

    12306的一種可能的模型,是異步來進(jìn)行的: 下單 -> 減庫存 和 訂單(redis kafka) 同時(shí)異步進(jìn)行 ->等付款,付完款,持久化到Hbase, MySQL等等

    減庫存最后還會(huì)把壓力壓到一臺(tái)服務(wù)器,怎么辦?可以做分布式本地庫存 + 單獨(dú)服務(wù)器做庫存均衡

    大流量的處理方法:分而治之,每臺(tái)機(jī)器只減自己機(jī)器上有的庫存

    流量?jī)A斜的問題怎么解決?比如有的機(jī)器上已經(jīng)沒庫存了,有的機(jī)器上還剩很多?這時(shí)候你還需要一臺(tái)單獨(dú)的服務(wù)器,去做所有服務(wù)器的平衡,如果某臺(tái)服務(wù)器沒庫存了,從別的機(jī)器上挪一些過去。

    2、優(yōu)化運(yùn)行JVM運(yùn)行環(huán)境(慢,卡頓)
    案例1:升級(jí)內(nèi)存后反而網(wǎng)站更卡

    有一個(gè)50萬PV的文檔資料類網(wǎng)站(從磁盤提取文檔到內(nèi)存)原服務(wù)器32位,1.5G的堆,用戶反饋網(wǎng)站比較緩慢。因此公司決定升級(jí),新的服務(wù)器為64位,16G的堆內(nèi)存,結(jié)果用戶反饋卡頓十分嚴(yán)重,反而比以前效率更低了!

  • 為什么原網(wǎng)站慢?

    因?yàn)楹芏嘤脩魹g覽數(shù)據(jù),很多用戶瀏覽導(dǎo)致很多數(shù)據(jù)Load到內(nèi)存,產(chǎn)生了很多文檔對(duì)應(yīng)的Java包裝對(duì)象(而不是文檔對(duì)象,文檔本身可以走Nginx)。內(nèi)存不足,頻繁GC,STW長(zhǎng),響應(yīng)時(shí)間變慢

  • 為什么會(huì)更卡頓?

    內(nèi)存越大,FGC時(shí)間越長(zhǎng)

  • 怎么解決?

    PS 換成 PN + CMS,或者 G1
    或者業(yè)務(wù)上的調(diào)整,文檔不走JVM

  • 案例2:系統(tǒng)CPU經(jīng)常100%,如何排查、調(diào)優(yōu)?

    推理過程是:CPU 100%,那么一定有線程在占用系統(tǒng)資源,所以

  • 找出哪個(gè)進(jìn)程cpu高(top 命令)

  • 該進(jìn)程中的哪個(gè)線程cpu高(top -Hp)

  • 如果是java程序,導(dǎo)出該線程的堆棧 (jstack命令,列出當(dāng)前程序有哪些線程,以及線程編號(hào),使用arthas工具可以heapdump導(dǎo)出堆棧),導(dǎo)出之后就可以用圖形界面了,也可以使用 jmap 分析堆文件,看哪個(gè)對(duì)象占用內(nèi)存過多,再去分析業(yè)務(wù)邏輯。生產(chǎn)環(huán)境中,jmap 命令比 archas 好用一些,但是都會(huì)導(dǎo)致服務(wù)暫停。生產(chǎn)環(huán)境一般直接配置參數(shù) -XX:+HeapDumpOnOutOfMemoryError

    archas 用的是 agent,可以線上替換 class

  • (查找哪個(gè)方法(棧幀)消耗時(shí)間,哪個(gè)方法調(diào)用的哪個(gè)方法 (jstack),然后去看這個(gè)方法的代碼)

  • 要區(qū)分是業(yè)務(wù)線程占比高 / 垃圾回收線程占比高?

  • 案例3:系統(tǒng)內(nèi)存飆高,如何查找問題?
  • 導(dǎo)出堆內(nèi)存 (jmap)
  • 分析 (jhat jvisualvm mat jprofiler … )
  • 如何監(jiān)控 JVM?

    可以使用 jstat jvisualvm jprofiler arthas top…等等

    3、解決JVM運(yùn)行過程中出現(xiàn)的各種問題(不完全等同于解決OOM的問題,因?yàn)榍懊鎯身?xiàng)也很重要)
    OOM 案例
    • C++ 轉(zhuǎn) Java 的程序員重寫了 finalize 方法,當(dāng)一個(gè)對(duì)象被回收的時(shí)候,這個(gè)函數(shù)默認(rèn)被調(diào)用,如果你在這個(gè)函數(shù)中寫了很多業(yè)務(wù)邏輯,回收這個(gè)對(duì)象就要花好長(zhǎng)時(shí)間,對(duì)象產(chǎn)生速度大于回收速度,導(dǎo)致了 OOM

    [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-GSuTQaAf-1604373530280)(images/image-20200829191206740.png)]

    總結(jié)

    以上是生活随笔為你收集整理的面试必会系列 - 1.6 Java 垃圾回收机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

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