JVM

1、從編碼到執(zhí)行


JAVA從編碼到執(zhí)行.png
  • 解釋執(zhí)行和編譯執(zhí)行是可以混合的,執(zhí)行次數(shù)多的代碼,會(huì)進(jìn)行 JIT 的編譯,交由操作系統(tǒng)直接執(zhí)行。
  • JVM 和 JAVA 無(wú)關(guān),只要可以編譯為 CLASS,都可在 JVM 上運(yùn)行。
  • 常見(jiàn) JVM:
    • Hotspot - Oracle 官方,最常用的 JVM。
    • Jrockit - BEA,曾經(jīng)號(hào)稱(chēng)最快的 JVM。被 Oracle 收購(gòu),合并于 Hotspot。
    • J9 - IBM。
    • Microsoft VM。
    • TaobaoVM - Hotspot 深度定制版。
    • LiquidVM - 直接針對(duì)硬件。
    • Azul Zing - 最新垃圾回收的夜間標(biāo)桿。官網(wǎng):https://www.azul.com/

JDK、JRE、JVM

  • JVM:Java Virtual Machine
  • JRE:JVM + Core lib
  • JDK:JRE + Development kit

2、Class文件結(jié)構(gòu)


  1. class 文件:是二進(jìn)制字節(jié)流。

  2. 查看 class 文件:javap -v X.class

CLASS文件結(jié)構(gòu) - HEX.png

3、類(lèi)加載過(guò)程


類(lèi)加載器

  • JVM 是按需動(dòng)態(tài)加載,采用雙親委派機(jī)制。

    主要是基于安全考慮:如果自定義 class 都可以 load 到內(nèi)存,客戶(hù)可以創(chuàng)建 java.lang.String 類(lèi),通過(guò) CustomClassLoader 覆蓋掉 Bootstrap 內(nèi)的類(lèi)文件。

    次要是防止資源浪費(fèi):如果已經(jīng)加載過(guò),查找使用即可,無(wú)需重復(fù)加載。

類(lèi)加載器.png
  • 打破雙親委派:

    1. 重寫(xiě) loadClass() 方法。
    2. ThreadContextClassLoader 可以實(shí)現(xiàn)基礎(chǔ)類(lèi)調(diào)用實(shí)現(xiàn)類(lèi)代碼,通過(guò) thread.setContextClassLoader 指定。
    3. 場(chǎng)景 - 熱啟動(dòng),熱部署:osgi tomcat 都有自己的模塊(web application)指定 classloader,可以加載同一類(lèi)庫(kù)的不同版本。
  • 查看 ClassLoader 加載路徑:

    // BootstrapClassLoader 加載路徑 
    System.getProperty("sun.boot.class.path");
    // ExtensionClassLoader 加載路徑
    System.getProperty("java.ext.dirs");
    // AppClassLoader 加載路徑
    System.getProperty("java.class.path");
    

類(lèi)加載過(guò)程

  1. Loading:class 文件 load 到內(nèi)存。
  2. Linking:鏈接。
    • Verification:校驗(yàn) class 是否符合 JVM 規(guī)范。
    • Preparation:靜態(tài)成員變量賦默認(rèn)值,不是初值。
    • Resolution:將常量池中類(lèi)、方法、屬性等符號(hào)引用解析為指針、偏移量等內(nèi)存地址的直接引用。
  3. Initializing:靜態(tài)變量賦初始值,調(diào)用靜態(tài)代碼塊,調(diào)用類(lèi)初始化代碼。
類(lèi)加載過(guò)程.png

CompilerAPI

可以手動(dòng)直接在內(nèi)存中編譯代碼,無(wú)需生成到磁盤(pán)。

LazyLoading

嚴(yán)格講應(yīng)該叫做 LazyInitializing。

JVM 規(guī)范并沒(méi)有規(guī)定何時(shí)加載,但是嚴(yán)格規(guī)定了什么時(shí)候必須初始化。

  1. new、getstatic、putstatic、invokestatic 指令,訪(fǎng)問(wèn) final 變量除外。

    getstatic:讀取靜態(tài)變量。

    putstatic:設(shè)置靜態(tài)變量。

    invokestatic:執(zhí)行靜態(tài)方法

  2. java.lang.reflect 對(duì)類(lèi)進(jìn)行反射調(diào)用時(shí)。

  3. 初始化子類(lèi)時(shí),父類(lèi)首先初始化。

  4. 虛擬機(jī)啟動(dòng)時(shí),被執(zhí)行的主類(lèi)必須初始化。

    包含 main 方法的類(lèi)。

  5. 動(dòng)態(tài)語(yǔ)言支持 java.lang.invoke.MethodHandler 解析的結(jié)果為 REF_getstatic、REF_putstatic、REF_invokestatic 的方法句柄時(shí),該類(lèi)必須初始化。

Java代碼執(zhí)行模式

Java 默認(rèn)是解釋執(zhí)行,jvm 發(fā)現(xiàn)某段代碼執(zhí)行頻率很高,則將其編譯為本地代碼。

  • 解釋器 - bytecode intepreter

  • JIT - Just In-Time compiler

  • 混合模式:混合使用解釋器 + 熱點(diǎn)代碼編譯。起始階段采用 解釋執(zhí)行。

    熱點(diǎn)代碼檢測(cè):

    1. 多次被調(diào)用的方法(方法計(jì)數(shù)器:檢測(cè)方法執(zhí)行頻率)

    2. 多次被調(diào)用的循環(huán)(循環(huán)計(jì)數(shù)器:檢測(cè)循環(huán)執(zhí)行頻率)

可以通過(guò)參數(shù)指定運(yùn)行模式:

  • -Xmixed 默認(rèn)為混合模式,開(kāi)始解釋執(zhí)行,啟動(dòng)速度較快,對(duì)熱點(diǎn)代碼實(shí)行檢測(cè)和編譯。
  • -Xint 使用解釋模式,啟動(dòng)很快,執(zhí)行稍慢。
  • -Xcomp 使用純編譯模式,執(zhí)行很快,啟動(dòng)很慢。
  • -XX:CompileThreshold=10000 檢測(cè)熱點(diǎn)代碼閾值。

4、JMM


  • Java Memory Model:Java 內(nèi)存模型

硬件層數(shù)據(jù)一致性

現(xiàn)代 CPU 數(shù)據(jù)一致性實(shí)現(xiàn)通過(guò)緩存鎖總線(xiàn)鎖實(shí)現(xiàn)。

CPU 緩存一致性協(xié)議:MSI、MESI、MOSI、Synapse、Firefly、Dragon。Intel CPU 使用的是 MESI 協(xié)議。

MESI 是緩存鎖實(shí)現(xiàn)方式之一,有些無(wú)法被緩存的數(shù)據(jù),或者跨越多個(gè)緩存行的數(shù)據(jù),依然必須使用總線(xiàn)鎖。

讀取緩存以 cache line 為基本單位,目前 64bytes。

CPU 每個(gè) cache line 標(biāo)記四種狀態(tài)(額外2位):

  • Modified:對(duì)緩存數(shù)據(jù)進(jìn)行了更改。
  • Exclusive:對(duì)緩存數(shù)據(jù)獨(dú)享。
  • Shared:對(duì)緩存數(shù)據(jù)讀共享。
  • Invalid:緩存數(shù)據(jù)被其他 CPU 進(jìn)行了更改。

偽共享

位于同一緩存航的兩個(gè)不同數(shù)據(jù),被兩個(gè)不同 CPU 鎖定,產(chǎn)生互相影響。

解決方案:緩存行對(duì)齊,能夠提高效率,但會(huì)浪費(fèi)一些空間。

// disruptor 多線(xiàn)程對(duì)指針游標(biāo)使用特別頻繁。
public long p1, p2, p3, p4, p5, p6, p7; // cache line padding
private volatile long cursor = INITIAL_CURSOR_VALUE;
public long p8, p9, p10, p11, p12, p13, p14; // cache line padding

CPU 亂序執(zhí)行

CPU 亂序執(zhí)行根源:為了提高指令執(zhí)行效率,讀等待同時(shí)指令執(zhí)行。讀指令等待的同時(shí),可以同時(shí)執(zhí)行不影響其他指令。而寫(xiě)的同時(shí)可以進(jìn)行合并寫(xiě)。這樣 CPU 的執(zhí)行就是亂序的。

必須使用 Memory Barrier 來(lái)做好指令排序。volatile 的底層就是這樣實(shí)現(xiàn)的(Windows 是 lock 指令)。防止亂序執(zhí)行,可以使用內(nèi)存屏障。

內(nèi)存屏障

Intel CPU 級(jí)別內(nèi)存屏障(不同 CPU,內(nèi)存屏障實(shí)現(xiàn)不同):

  • sfence:在 sfence 指令前的寫(xiě)操作當(dāng)必須在 sfence 指令后的寫(xiě)操作前完成。
  • lfence:在 lfence 指令前的讀操作當(dāng)必須在 lfence 指令后的讀操作前完成。
  • mfence:在 mfence 指令前的讀寫(xiě)操作當(dāng)必須在 mfence 指令后的讀寫(xiě)操作前完成。

JVM 級(jí)別內(nèi)存屏障規(guī)范(JSR33):

  • LoadLoad 屏障:對(duì)于這樣的語(yǔ)句 Loadl:LoadLoad:Load2,在 Load2 及后讀讀取操作要讀取的數(shù)據(jù)被訪(fǎng)問(wèn)前,保證 Load1 要讀取的數(shù)據(jù)被讀取完畢。
  • StoreStore 屏障:對(duì)于這樣的語(yǔ)句 Storel:StoreStore:Store2,在 Store2 及后續(xù)寫(xiě)入操怍執(zhí)行前,保證 Store1 的寫(xiě)入操怍對(duì)其它處理器可見(jiàn)。
  • LoddStore 屏障:對(duì)于這樣的語(yǔ)句 Loadl:LoadStore:Store2,在 Store2 及后續(xù)寫(xiě)入操作被刷出前,保證 Load1 要讀取的數(shù)據(jù)被讀取完畢。
  • StoreLoad 屏障:對(duì)于這樣的語(yǔ)句 Store1:StoreLoad:Load2,茌 Load2 及后續(xù)所有讀取操作執(zhí)行前,保證 Storel 的寫(xiě)入對(duì)所有處理器可見(jiàn)。

Volatile 實(shí)現(xiàn)細(xì)節(jié):

  • 字節(jié)碼層面:class 文件增加了 ACC_VOLITILE 標(biāo)識(shí)。

  • JVM 層面:volatile 內(nèi)存區(qū)的讀寫(xiě),都加屏障。

    StoreStoreBarrier
    volatile 寫(xiě)操作
    StoreLoadBarrier
    
    LoadLoadBarrier
    volatile 寫(xiě)操作
    LoadStoreBarrier
    
  • OS 和硬件層面:HSDIS - HotSpot Dis Assembler;Windows - lock 指令實(shí)現(xiàn)。

Synchronized 實(shí)現(xiàn)細(xì)節(jié):

  • 字節(jié)碼層面:ACC_SYNCHRONIZED、monitorenter、monitorexit。
  • JVM層面:調(diào)用了操作系統(tǒng)提供的同步機(jī)制。
  • OS 和硬件層面:X86 - lock comxchg / xxxx。

Hanppens-Before原則:]VM規(guī)定重排序必須遵守的規(guī)則

  • 程序次序規(guī)則:同一個(gè)線(xiàn)程內(nèi),按照代碼出現(xiàn)的順序,前面的代碼先行于后面的代碼,準(zhǔn)確的說(shuō)是控制流順序,因?yàn)橐紤]到分支和循環(huán)結(jié)構(gòu)。
  • 管程鎖定規(guī)則:一個(gè) unlock 操作先行發(fā)生于后面(時(shí)間上)對(duì)同一個(gè)鎖的 lock 操作。
  • volatile 變量規(guī)則:對(duì)一個(gè) volatile 變量的寫(xiě)操作先行發(fā)生于后面(時(shí)間上)對(duì)這個(gè)變量的讀操作。
  • 線(xiàn)程啟動(dòng)規(guī)則:Threadstart() 方法先行發(fā)生于這個(gè)線(xiàn)程的每一個(gè)操作。
  • 線(xiàn)程終止規(guī)則:線(xiàn)程的所有操作都先行于此線(xiàn)程的終止檢測(cè)??梢酝ㄟ^(guò) Thread.join() 方法結(jié)束、Thread.isAlive() 的返回值等手段檢測(cè)線(xiàn)程的終止。
  • 線(xiàn)程中斷規(guī)則:對(duì)線(xiàn)程 interrupt()方法的調(diào)用先行發(fā)生于被中斷線(xiàn)程的代碼檢測(cè)到中斷事件的發(fā)生,可以通過(guò) Thread.isInterrupted() 方法檢測(cè)線(xiàn)程是否中斷。
  • 對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行于發(fā)生它的 finalize() 方法的開(kāi)始。
  • 傳遞性:如果操作A先行于操作B,操作B先行于操作C,那么操作A先行于操作C。

AS IF SERIAL

不管如何重排序,單線(xiàn)程執(zhí)行結(jié)果不會(huì)改變。

對(duì)象的內(nèi)存布局

觀(guān)察虛擬機(jī)配置:java -XX:+PrintCommandLineFlags -version。

普通對(duì)象:

  1. 對(duì)象頭:markword

    • 32位 - 4字節(jié)
markword-32bit.png
  • 64位 - 8字節(jié)
markword-64bit.png
  1. ClassPointer 指針:開(kāi)啟 -XX:+UseCompressedClassPointers 為4字節(jié),不開(kāi)啟為8字節(jié)。默認(rèn)開(kāi)啟。

    可以使用 -XX:-UseCompressedClassPointers 關(guān)閉。

  2. 實(shí)例數(shù)據(jù):普通對(duì)象指針。開(kāi)啟 -XX:+UseCompressedOops 為4字節(jié),不開(kāi)啟為8字節(jié)。默認(rèn)開(kāi)啟。

    Oops:Ordinary Object Pointers。

  3. Padding 對(duì)齊:8的倍數(shù)。

數(shù)組對(duì)象:在普通對(duì)象基礎(chǔ)上,多了一個(gè)數(shù)組長(zhǎng)度,4字節(jié)。

HashCode 和 偏向鎖

  • 當(dāng)一個(gè)對(duì)象已經(jīng)計(jì)算過(guò)identity hash code,它就無(wú)法進(jìn)入偏向鎖狀態(tài);
  • 當(dāng)一個(gè)對(duì)象當(dāng)前正處于偏向鎖狀態(tài),并且需要計(jì)算其identity hash code的話(huà),則它的偏向鎖會(huì)被撤銷(xiāo),并且鎖會(huì)膨脹為重量鎖;
  • 重量鎖的實(shí)現(xiàn)中,ObjectMonitor類(lèi)里有字段可以記錄非加鎖狀態(tài)下的mark word,其中可以存儲(chǔ)identity hash code的值?;蛘吆?jiǎn)單說(shuō)就是重量鎖可以存下identity hash code。

請(qǐng)一定要注意,這里討論的hash code都只針對(duì)identity hash code。用戶(hù)自定義的hashCode()方法所返回的值跟這里討論的不是一回事。
Identity hash code是未被覆寫(xiě)的 java.lang.Object.hashCode() 或者 java.lang.System.identityHashCode(Object) 所返回的值。

對(duì)象定位

  1. 句柄池
  2. 直接指針:Hotspot 使用的是直接指針。

GC 回收時(shí),定位方式有影響,三色標(biāo)記算法,對(duì)句柄池算法效率比較高,對(duì)直接指針?biāo)惴ㄐ时容^低。

對(duì)象分配過(guò)程

  • 棧上分配:線(xiàn)程私有小對(duì)象,無(wú)逃逸(對(duì)象僅在方法內(nèi)有引用),支持標(biāo)量替換。默認(rèn)開(kāi)啟。

  • 線(xiàn)程本地分配:TLAB(Thread Local Allocation Buffer)。小對(duì)象,占用 Eden 區(qū),默認(rèn) 1%。多線(xiàn)程情況下不用競(jìng)爭(zhēng) Eden 就可以申請(qǐng)空間,提高效率。

  • 老年代:大對(duì)象。

  • 棧上分配 和 線(xiàn)程本地分配一般不需要調(diào)整參數(shù)。

  • 相關(guān) JVM 啟動(dòng)參數(shù):

    -XX:-EscapeAnalysis :關(guān)閉逃逸分析。

    -XX:-EliminateAllocations:關(guān)閉標(biāo)量替換。

    -XX:-UseTLAB:關(guān)閉TLAB。

對(duì)象分配過(guò)程.png

5、運(yùn)行時(shí)數(shù)據(jù)區(qū)


Run-time Data Areas

Run-time data areas.png
線(xiàn)程共享區(qū)域.png
  • Program Counter:程序計(jì)數(shù)器 - 存放指令位置。每個(gè)線(xiàn)程有自己的 PC。

  • JVM stacks:每個(gè)線(xiàn)程有獨(dú)有的棧,線(xiàn)程棧內(nèi)裝載的是棧幀,每個(gè)方法調(diào)用對(duì)應(yīng)一個(gè)棧幀。

  • native method stacks:native 方法棧。

  • Direct Memory:JVM 直接訪(fǎng)問(wèn)內(nèi)核空間內(nèi)存(OS 管理的內(nèi)存),省略了內(nèi)存拷貝的過(guò)程。
    NIO,提交效率,實(shí)現(xiàn) zero copy。

  • Method area:方法區(qū)。裝載 class、常量池。方法區(qū)在所有 JVM 線(xiàn)程間共享。方法區(qū)是邏輯概念,PermSpace 和 MetaSpace 是具體實(shí)現(xiàn)。

    1.8 版本前:Perm Space,F(xiàn)GC 不回收。字符串常量位于 PermSpace。啟動(dòng)時(shí)指定,不可變。

    1.8 版本后:Meta Space,字符串常量位于堆,會(huì)觸發(fā) FGC 清理。如果不設(shè)定,最大是物理內(nèi)存。

  • Heap:在所有 JVM 線(xiàn)程間共享。堆是運(yùn)行時(shí)數(shù)據(jù)區(qū),為所有類(lèi)實(shí)例和數(shù)組分配內(nèi)存。

  • run-time constant pool:常量池?cái)?shù)據(jù),裝載在運(yùn)行時(shí)常量池內(nèi)。

堆內(nèi)邏輯分區(qū)

內(nèi)存模型:除 Epsilon、ZGC、shenandoah 之外,都是使用邏輯分代,其中 G1 是邏輯分代,物理不分代,除此之外,不僅邏輯分代,物理也分代。內(nèi)存分區(qū)不適用不分代垃圾收集器,例如 ZGC(jdk11)、Shenandoah(jdk12)。

內(nèi)存布局:新生代、老年代、方法區(qū)(MethodArea)

  1. 方法區(qū)是邏輯概念,1.7版本永久代(Perm Generation)實(shí)現(xiàn),1.8版本元數(shù)據(jù)區(qū)(Metaspace)實(shí)現(xiàn)。
  2. 永久代需要啟動(dòng)時(shí)指定大小,且不可更改。元數(shù)據(jù)區(qū)可以設(shè)置,也可以不設(shè)置,受限于物理內(nèi)存。
  3. 永久代/元數(shù)據(jù)區(qū) 存放:Class 元信息、方法編譯后信息、代碼編譯后信息、JIT 編譯信息、字節(jié)碼等。
  4. 字符串常量:1.7 在永久代,1.8 在堆。
  5. 1.8 版本 新生代/老年代 內(nèi)存比例默認(rèn) 1 : 2??赏ㄟ^(guò)參數(shù) -XX:NewRatio=2 調(diào)整。
堆內(nèi)存邏輯分區(qū).png
  • MinorGC / YGC:新生代空間耗盡時(shí)觸發(fā)。
  • MajorGC / FullGC:老年代滿(mǎn)了,無(wú)法繼續(xù)分配空間時(shí)觸發(fā),新生代老年代同事進(jìn)行回收。
  • 新生代大量死去,少量存活,采用復(fù)制算法。
  • 老年代存活率高,回收較少,采用標(biāo)記清除或標(biāo)記壓縮算法。

Eden 區(qū)經(jīng)過(guò)回收之后,進(jìn)入 Survivor 區(qū)。在 Survivor 區(qū)達(dá)到 -XX:MaxTenuringThreshold 參數(shù)閾值后(最大15),進(jìn)入 Tunured 區(qū)。

動(dòng)態(tài)年齡:S0 向 S1 復(fù)制時(shí),超過(guò) 50%,把年齡最大的放入老年代。

空間擔(dān)保/分配擔(dān)保:YGC 期間,Survivor 區(qū)空間不夠了,直接進(jìn)入老年代。

棧幀

框架用于存儲(chǔ)數(shù)據(jù)和中間結(jié)果,以及執(zhí)行動(dòng)態(tài)鏈接、方法返回值和異常。

  • Local Variables Table:局部變量表。

  • Operand Stacks:操作數(shù)棧。

  • Dynamic Linking:指向運(yùn)行時(shí)常量池的符號(hào)鏈接。

    A() -> B(),B 方法要到常量池找,B 方法調(diào)用在棧幀上就是一個(gè) Dynamic Linking。

  • Return Address:A() -> B(),B 方法的返回值應(yīng)存放的位置。可以理解為方法出口。

指令集

指令集設(shè)計(jì)有兩種類(lèi)型:

  • 基于棧的指令集:JVM 基于棧的指令集設(shè)計(jì)。
  • 基于寄存器的指令集。

Hotspot 的 Local variable table 類(lèi)似于寄存器。

6、GC基本算法


垃圾定義:沒(méi)有任何引用指向的對(duì)象。

查找算法

  • Reference Count:引用計(jì)數(shù)。不能解決循環(huán)引用問(wèn)題。

  • Root Searching:根可達(dá)算法??勺鳛?root 的對(duì)象包括:

    1. JVM stack:線(xiàn)程棧變量
    2. native method stack:JNI 指針
    3. run-time constant pool:常量池
    4. static references in method area:靜態(tài)變量 - 方法區(qū)內(nèi)部靜態(tài)引用
    5. class

回收算法

  • Mark-Sweep:標(biāo)記清除 - 存活對(duì)象比較多的情況下,效率比較高,不適合 Eden 區(qū)。需要兩次掃描,第一次標(biāo)記,第二次清除,效率偏低。容易產(chǎn)生碎片。
Mark-Sweep.png
  • Copying:拷貝 - 適用于存活對(duì)象較少的情況,例如 Eden 區(qū)。只需要掃描一次,效率提高,沒(méi)有碎片。缺點(diǎn)是空間浪費(fèi),需要移動(dòng)和復(fù)制對(duì)象,指向?qū)ο蟮囊眯枰{(diào)整。
Copying.png
  • Mark-Compact:標(biāo)記壓縮 - 空間連續(xù),沒(méi)有碎片,不會(huì)產(chǎn)生內(nèi)存浪費(fèi)。需要掃描兩次,并移動(dòng)數(shù)據(jù),效率偏低。
Mark-Compact.png

7、垃圾回收器


GC.png

常見(jiàn)垃圾回收器:1.8 版本默認(rèn) PS + ParallelOld。

  1. Serial:年輕代,串行回收,單線(xiàn)程設(shè)計(jì)。單 CPU 效率最高。

  2. SerialOld:老年代,串行回收,單線(xiàn)程設(shè)計(jì),使用 mark-sweep-compact 算法。

  3. ParallelScavenge:年輕代,串行回收,多線(xiàn)程設(shè)計(jì)。

  4. ParallelOld:老年代,串行回收,多線(xiàn)程設(shè)計(jì),使用 mark-compact 算法。

  5. ParNew:年輕代,配合 CMS 的并行回收?;?PS 做了增強(qiáng),例如 CMS 某些特定階段,ParNew 會(huì)同時(shí)運(yùn)行。

  6. CMS:ConcurrentMarkSweep,老年代,并發(fā)的。垃圾回收和應(yīng)用程序同時(shí)運(yùn)行,降低 STW 時(shí)間(200ms)。

    CMS 使用標(biāo)記清除,一定會(huì)產(chǎn)生碎片,碎片達(dá)到一定程度,使用 SerialOld 進(jìn)行老年代回收。

    1.4 版本開(kāi)始支持,CMS 問(wèn)題較多,目前沒(méi)有任何版本默認(rèn)支持,需要手動(dòng)開(kāi)啟。

  7. G1:算法 - f + SATB。

  8. ZGC:算法 - ColloredPointers + 寫(xiě)屏障。

  9. Shenandoah:算法 - ColloredPointers + 讀屏障。

  10. Eplison:一般調(diào)試時(shí)使用。

GC 和內(nèi)存大小的關(guān)系:

  1. Serial:100Mb 以?xún)?nèi)
  2. PS:100Mb - 幾個(gè)Gb
  3. CMS:20Gb
  4. G1:上百Gb
  5. ZGC:4Tb

CMS

CMS線(xiàn)程角度.png
  • 運(yùn)行階段(實(shí)際6個(gè),另外兩個(gè)不重要)

    • 初始標(biāo)記:?jiǎn)尉€(xiàn)程。直接找到最根上的對(duì)象,會(huì)產(chǎn)生 STW,但運(yùn)行很快。
    • 并發(fā)標(biāo)記:最浪費(fèi)時(shí)間的階段,和應(yīng)用線(xiàn)程同時(shí)運(yùn)行。
    • 重新標(biāo)記:多線(xiàn)程。會(huì)產(chǎn)生 STW,多數(shù)垃圾在并發(fā)標(biāo)記過(guò)程中,標(biāo)記完成之后產(chǎn)生的新垃圾,進(jìn)行重新標(biāo)記。新垃圾不多,停頓時(shí)間很短。
    • 并發(fā)清理:清理過(guò)程中會(huì)產(chǎn)生浮動(dòng)垃圾,需要等待下次 CMS 運(yùn)行,再進(jìn)行清理。
  • 缺點(diǎn):

    1. Memory Fragmentation:內(nèi)存碎片化。 -XX:+UseCMSCampactAtFullCollection -XX:CMSFullGCsBeforeCompaction 可優(yōu)化此問(wèn)題。
    2. Floating Garbage:浮動(dòng)垃圾。解決方案:降低觸發(fā) CMS 的閾值。-XX:CMSInitiatingOccupancyFraction 92%,可以降低閾值,保持老年代有足夠空間。
    3. CMS 的設(shè)計(jì)并不是應(yīng)對(duì)大內(nèi)存,由于內(nèi)存碎片化,無(wú)法分配大對(duì)象,會(huì)啟動(dòng) SerialOld。
  • 日志分析

    [GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
      //8511 (13696) : 老年代使用(最大)
      //9866 (19840) : 整個(gè)堆使用(最大)
    [CMS-concurrent-mark-start]
    [CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
      //這里的時(shí)間意義不大,因?yàn)槭遣l(fā)執(zhí)行
    [CMS-concurrent-preclean-start]
    [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
      //標(biāo)記Card為Dirty,也稱(chēng)為Card Marking
    [GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
      //STW階段,YG occupancy:年輕代占用及容量
      //[Rescan (parallel):STW下的存活對(duì)象標(biāo)記
      //weak refs processing: 弱引用處理
      //class unloading: 卸載用不到的class
      //scrub symbol(string) table: 
          //cleaning up symbol and string tables which hold class-level metadata and 
          //internalized string respectively
      //CMS-remark: 8511K(13696K): 階段過(guò)后的老年代占用及容量
      //10108K(19840K): 階段過(guò)后的堆占用及容量
    [CMS-concurrent-sweep-start]
    [CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
      //標(biāo)記已經(jīng)完成,進(jìn)行并發(fā)清理
    [CMS-concurrent-reset-start]
    [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
      //重置內(nèi)部結(jié)構(gòu),為下次GC做準(zhǔn)備
    

G1

  • G1 GC:Garbage First Garbage Collector - 主要運(yùn)行在server端的,目標(biāo)是在多核、大內(nèi)存服務(wù)器上,通過(guò)并發(fā)和并行的手段,達(dá)到暫停時(shí)間比較短,維持不錯(cuò)的吞吐量。當(dāng)開(kāi)始 GC 過(guò)程時(shí),優(yōu)先收集垃圾最多的 Regions。G1 還是一種帶壓縮的收集器,在回收老年代分區(qū)時(shí),將存活對(duì)象從一個(gè)分區(qū),拷貝到另一個(gè)分區(qū),實(shí)現(xiàn)了局部壓縮。

    G1 新生代、老年代比例是動(dòng)態(tài)的,一般不要手工指定,因?yàn)檫@是 G1 預(yù)測(cè)停頓時(shí)間的基準(zhǔn)。

  • 特點(diǎn)

    • 并發(fā)收集 - 并發(fā)標(biāo)記、并發(fā)回收。
    • 壓縮空閑空間不會(huì)延長(zhǎng) GC 的暫停時(shí)間。
    • 更易預(yù)測(cè)的 GC 暫停時(shí)間。
  • 使用不需要實(shí)現(xiàn)很高的吞吐量的場(chǎng)景。

  • 觸發(fā)

    • YGC:Eden 空間不足,多線(xiàn)程并行執(zhí)行。

    • MixedGC:相當(dāng)于 CMS,YGC之后 ,堆內(nèi)存空間超過(guò)閾值,就會(huì)啟動(dòng)。閾值通過(guò) -XX:InitiatingHeapOccupancyPercent=45 設(shè)置,默認(rèn) 45%。

    • FullGC:Old 空間不足,或手動(dòng)調(diào)用System.GC()。

      優(yōu)化方案:擴(kuò)內(nèi)存;提高CPU性能(產(chǎn)生對(duì)象的速度固定,GC 越快,內(nèi)存空間越大);降低 MixedGC 觸發(fā)的閾值,讓 MixedGC 提早發(fā)生(默認(rèn)45%)。

      JDK10 之前使用的是 Serial,串行回收,調(diào)優(yōu)目標(biāo)是盡量不要產(chǎn)生 FGC。

  • MixedGC 過(guò)程

    • 初始標(biāo)記:STW
    • 并發(fā)標(biāo)記
    • 最終標(biāo)記:STW(重新標(biāo)記)
    • 篩選回收:STW(并行)
  • Region:G1 內(nèi)存模型邏輯分代,物理不分代。每個(gè)分區(qū)從 1M 到 32M 不等,但都是 2 的冪次方?;A(chǔ)分區(qū)大小可通過(guò)參數(shù)配置。

G1-Regions.png
  • CSset:Collection Set,一組可被回收的分區(qū)的集合,可理解為待回收的 Region 集合。在CSet中存活的數(shù)據(jù)會(huì)在 GC 過(guò)程中被移動(dòng)到另一個(gè)可用分區(qū),CSet 中的分區(qū)可以來(lái)自 Eden 空間、Survivor 空間、或者老年代。CSet 會(huì)占用不到整個(gè)堆空間的 1% 大小。

  • RSet:Remembered Set,記錄了其他 Region 中的對(duì)象到本 Region 的引用,RSet 的價(jià)值在于使得 GC 不需要掃描整個(gè)堆找到誰(shuí)引用了當(dāng)前分區(qū)中的對(duì)象,只需要掃描 RSet 即可。
    由于 RSet 的存在,每次給對(duì)象賦值引用時(shí),需要在 RSet 中做一些額外的記錄,在 GC 中被稱(chēng)為寫(xiě)屏障(不是 JVM 的內(nèi)存屏障)。

  • CardTable:卡表。由于 YGC 時(shí),Y區(qū)對(duì)象可能由 OLD 區(qū)對(duì)象引用,因此需要掃描整個(gè) OLD 區(qū),效率非常低,所以 JVM 設(shè)計(jì)了 CardTable。如果一個(gè) OLD 區(qū) CardTable 中有對(duì)象指向 Y 區(qū),就將它設(shè)為 Dirty,下次掃描時(shí),只需要掃描 Dirty Card。Card Table 使用 BitMap 實(shí)現(xiàn)。堆劃分為相等大小的一個(gè)個(gè)區(qū)域,這個(gè)小的區(qū)域(一般 size 在128-512字節(jié))被當(dāng)做 Card,而 Card Table 維護(hù)著所有的 Card。

  • 三色標(biāo)記

    • 白色:未被標(biāo)記的對(duì)象。
    • 灰色:自身被標(biāo)記,成員變量未被標(biāo)記。
    • 黑色:自身和成員變量均已標(biāo)記完成。

    漏標(biāo)問(wèn)題:在 Remark 過(guò)程中,黑色指向了白色,且灰色指向白色的引用消失了,如果不對(duì)黑色重新掃描,則會(huì)漏標(biāo),會(huì)把白色對(duì)象當(dāng)做沒(méi)有新引用指向,從而回收掉。

三色標(biāo)記-漏標(biāo).png

解決漏標(biāo)問(wèn)題有兩種方案:

  1. Incremental Update - 增量更新,關(guān)注引用的增加,把黑色重新標(biāo)記為灰色,下次重新掃描屬性。CMS 使用此方案。

  2. SATB - Snapshot at the beginning - 關(guān)注引用的刪除,當(dāng)灰色對(duì)象指向白色對(duì)象引用消失時(shí),要把這個(gè)引用推到 GC 的堆棧,保證白色對(duì)象還能被 GC 掃描到。

    GC 棧中存放的是灰色對(duì)象指向白色對(duì)象的引用。

G1 使用的是 SATB,只需要把改變過(guò)的引用重新掃描即可。當(dāng)再次掃描白色對(duì)象時(shí),僅需判斷白色對(duì)象所在 Region 的 RSet 是否有引用指向該對(duì)象,不需要掃描整個(gè)堆,即可標(biāo)記該對(duì)象是否為垃圾。SATB 配合 RSet 使用,渾然天成。

  • 日志分析

    [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]
    //young -> 年輕代 Evacuation-> 復(fù)制存活對(duì)象 
    //initial-mark 混合回收的階段,這里是YGC混合老年代回收
       [Parallel Time: 1.5 ms, GC Workers: 1] //一個(gè)GC線(xiàn)程
          [GC Worker Start (ms):  92635.7]
          [Ext Root Scanning (ms):  1.1]
          [Update RS (ms):  0.0]
             [Processed Buffers:  1]
          [Scan RS (ms):  0.0]
          [Code Root Scanning (ms):  0.0]
          [Object Copy (ms):  0.1]
          [Termination (ms):  0.0]
             [Termination Attempts:  1]
          [GC Worker Other (ms):  0.0]
          [GC Worker Total (ms):  1.2]
          [GC Worker End (ms):  92636.9]
       [Code Root Fixup: 0.0 ms]
       [Code Root Purge: 0.0 ms]
       [Clear CT: 0.0 ms]
       [Other: 0.1 ms]
          [Choose CSet: 0.0 ms]
          [Ref Proc: 0.0 ms]
          [Ref Enq: 0.0 ms]
          [Redirty Cards: 0.0 ms]
          [Humongous Register: 0.0 ms]
          [Humongous Reclaim: 0.0 ms]
          [Free CSet: 0.0 ms]
       [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)]
     [Times: user=0.00 sys=0.00, real=0.00 secs] 
    //以下是混合回收其他階段
    [GC concurrent-root-region-scan-start]
    [GC concurrent-root-region-scan-end, 0.0000078 secs]
    [GC concurrent-mark-start]
    //無(wú)法evacuation,進(jìn)行FGC
    [Full GC (Allocation Failure)  18M->18M(20M), 0.0719656 secs]
       [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38
    76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]
    

ZGC

ZGC的核心是 Colored Pointer + Load Barrier,不支持32位,不支持指針壓縮。

Collored Pointer:顏色指針,GC信息記錄在指針上,不記錄在頭部,Immediate memory use。

JDK12及以前,使用42位指針,尋址空間4T;JDK13及以后,擴(kuò)展為 2^{44},16T,因?yàn)镃PU地址總線(xiàn)最大支持48位。

Marked0、Marked1、Remapped、Finalizable 是互斥的,同時(shí)只能有一位為1,普通對(duì)象全部為0。

ZGC-Object-Pointer.png

Load Barrier 根據(jù)指針顏色決定是否做一些事情。

ZGC 可以做到 NUMA(Non Uniform Memory Access) Aware。

8、JVM調(diào)優(yōu)


確定調(diào)優(yōu)之前,應(yīng)該確定是吞吐量?jī)?yōu)先(計(jì)算型任務(wù)),還是響應(yīng)時(shí)間優(yōu)先(響應(yīng)型任務(wù)),還是在滿(mǎn)足一定響應(yīng)時(shí)間的情況下,要求達(dá)到多大的吞吐量。

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

    例如科學(xué)計(jì)算、數(shù)據(jù)挖掘。GC 一般選擇 PS + PO。

  • 響應(yīng)時(shí)間:用戶(hù)線(xiàn)程停頓的時(shí)間短

    STW 越短,響應(yīng)時(shí)間越好。例如網(wǎng)站、API服務(wù)。GC 一般選擇 G1。

調(diào)優(yōu)思路

  1. 根據(jù)需求進(jìn)行 JVM 規(guī)劃和預(yù)調(diào)優(yōu)。
  2. 優(yōu)化運(yùn)行 JVM 運(yùn)行環(huán)境。
  3. 解決 JVM 運(yùn)行過(guò)程中出現(xiàn)的各種問(wèn)題。

從規(guī)劃開(kāi)始

  • 調(diào)優(yōu),從業(yè)務(wù)場(chǎng)景開(kāi)始,沒(méi)有業(yè)務(wù)場(chǎng)景的調(diào)優(yōu)都是耍流氓。

  • 無(wú)監(jiān)控(壓力測(cè)試,能看到結(jié)果),不調(diào)優(yōu)。

  • 步驟:

    1. 熟悉業(yè)務(wù)場(chǎng)景(沒(méi)有最好的垃圾回收器,只有最合適的垃圾回收器)。

      確定追求吞吐量,還是響應(yīng)時(shí)間。

    2. 選擇回收器組合。

    3. 計(jì)算內(nèi)存需求(很難計(jì)算,經(jīng)驗(yàn)值,或經(jīng)過(guò)測(cè)試和監(jiān)控確定)。

    4. 選定 CPU(越高越好)。

    5. 設(shè)定年代大小、升代年齡。

    6. 設(shè)定日志參數(shù):

      -Xloggc:/opt/xxx-xxx-gc-%t.log -XX:+useGCLogFileRotation -XX:NumberOfGCLogFiles=5

      -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause

    7. 觀(guān)察日志情況。

百萬(wàn)并發(fā)

淘寶 2019 年雙 11,最大支撐 TPS 54萬(wàn)。12306 號(hào)稱(chēng) 百萬(wàn)并發(fā)。

9、OOM問(wèn)題


排查過(guò)程中,jconsole、jvisualvm 等圖形化界面工具,僅僅適用于開(kāi)發(fā)及測(cè)試,不適合排查生產(chǎn)問(wèn)題,因?yàn)镴MX連接到服務(wù)后,對(duì)服務(wù)器影響很高。一般圖形化界面只適用于系統(tǒng)上線(xiàn)前壓測(cè)監(jiān)控。

如果生產(chǎn)在線(xiàn)定位,生產(chǎn)一般做了高可用,停掉一臺(tái)服務(wù)器,對(duì)其他服務(wù)器無(wú)影響,因此先進(jìn)行隔離,停止該服務(wù)器對(duì)外提供服務(wù),流量降為0之后,基于此服務(wù)器在線(xiàn)定位,進(jìn)行分析。

OOM類(lèi)型

  1. 堆溢出:java.lang.OutOfMemoryError: Java heap space。
  2. 棧溢出:-Xss 設(shè)定太小,java.lang.StackOverflowError。
  3. 方法區(qū)溢出:java.lang.OutOfMemoryError: Compressed class space。
  4. 直接內(nèi)存溢出:使用Unsafe分配直接內(nèi)存,或者使用NIO的問(wèn)題。

排查思路

  1. top 命令觀(guān)察到問(wèn)題:內(nèi)存不斷增長(zhǎng),CPU占用率居高不下。

  2. top -Hp pid 命令觀(guān)察進(jìn)程中的線(xiàn)程,哪個(gè)線(xiàn)程CPU和內(nèi)存占比高。

  3. printf %x pid 將10進(jìn)制PID轉(zhuǎn)換為16進(jìn)制。

  4. jstack -l pid 查看進(jìn)程內(nèi)線(xiàn)程狀態(tài)。

    • nid:16進(jìn)制線(xiàn)程ID。
    • waiting on <0x0000000088拆310> (a java.lang.Object) :要找到哪個(gè)線(xiàn)程持有此鎖。
  5. jps 定位具體java進(jìn)程。

  6. jinfo pid 查看進(jìn)程JVM詳細(xì)信息。

  7. jstat -gc pid 500 每500毫秒打印 GC 情況,動(dòng)態(tài)觀(guān)察 GC 情況,閱讀 GC 日志發(fā)現(xiàn)頻繁GC。

    響應(yīng)信息很不直觀(guān),所以不常用,可通過(guò) arthas 、jconsole 等工具觀(guān)察。

  8. jmap -histo pid | head -20 查找有多少對(duì)象產(chǎn)生。此命令對(duì)在線(xiàn)系統(tǒng)影響不是很高。

    此步驟很關(guān)鍵,數(shù)量很多的對(duì)象,往往是造成 Heap OOM 的問(wèn)題所在。

    Arthas 目前未提供此功能。

  9. jmap -dump:format=b,file=xxx pid / jmap -histo 無(wú)論進(jìn)程是否卡頓了,只要進(jìn)程在,就可以導(dǎo)出。

    執(zhí)行此命令,對(duì)在線(xiàn)系統(tǒng)的影響特別高。

  10. java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError -jar xxx.jar Heap OOM 時(shí),自動(dòng)產(chǎn)生堆轉(zhuǎn)儲(chǔ)文件。

  11. 使用 MAT / jhat 進(jìn)行 dump 文件分析。

    jhat -J-Xmx512M heap.hprof 默認(rèn)開(kāi)啟 http 7000 端口,可在瀏覽器查看分析結(jié)果。

  12. 找到代碼的問(wèn)題。

jconsole 遠(yuǎn)程連接

  1. 程序啟動(dòng)加入?yún)?shù)

    java -Djava.rmi.server.hostname=192.168.17.11 \
         -Dcom.sun.management.jmxremote \
         -Dcom.sun.management.jmxremote.port=11111 \
         -Dcom.sun.management.jmxremote.authenticate=false \
         -Dcom.sun.management.jmxremote.ssl=false -jar Test.jar
    
  2. 如果遇到 Local host name unknown: XXX 的錯(cuò)誤,修改 /etc/hosts 文件,把 XXX 加入進(jìn)去

    192.168.17.11 basic localhost localhost.localdomain localhost4 1ocalhost4.1ocaldomain4
    ::1 localhost 1ocalhost.1ocaldomain localhost6 1ocalhost6.1ocaldomain6
    

jvisualvm 遠(yuǎn)程連接

可以連接 JMX,進(jìn)行實(shí)時(shí)監(jiān)控。

可以進(jìn)行 Heap dump 文件分析。

抽樣器:內(nèi)存監(jiān)控 - 觀(guān)察此界面哪些對(duì)象很多,且不斷增長(zhǎng),GC回收不掉,一定是相關(guān)類(lèi)代碼出了問(wèn)題。

線(xiàn)程狀態(tài)說(shuō)明:

  • 運(yùn)行:線(xiàn)程運(yùn)行中,對(duì)應(yīng) JSTACK 中 RUNNABLE。
  • 休眠:對(duì)應(yīng) sleep 操作。
  • 等待:對(duì)應(yīng) wait 操作。
  • 駐留:對(duì)應(yīng)線(xiàn)程池里的空閑線(xiàn)程。
  • 監(jiān)視:對(duì)應(yīng)的 synchronized 阻塞。

jprofiler

號(hào)稱(chēng)是最好用的,但是收費(fèi)。

10、JSTACK線(xiàn)程狀態(tài)


  • RUNNABLE:線(xiàn)程運(yùn)行中或I/O等待。

    public static void runnable() {
        long i = 0;
        while (true) {
            i++;
        }
    }
    
  • BLOCKED:等待互斥量或鎖的釋放。線(xiàn)程在等待monitor鎖(synchronized關(guān)鍵字)。

    public static void blocked() {
        final Object lock = new Object();
        new Thread() {
            public void run() {
                synchronized (lock) {
                    System.out.println("i got lock, but don't release");
                    try {
                        Thread.sleep(1000L * 1000);
                    } catch (InterruptedException e) {
                    }
                }
            }
        }.start();
    
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
    
        synchronized (lock) {
            try {
                Thread.sleep(30 * 1000);
            } catch (InterruptedException e) {
            }
        }
    }
    
  • TIMED_WAITING:線(xiàn)程在等待喚醒,但設(shè)置了時(shí)限。Lock.tryLock(30000) 也會(huì)觸發(fā)此狀態(tài)。

    public static void timedWaiting() {
        final Object lock = new Object();
        synchronized (lock) {
            try {
                lock.wait(30 * 1000);
            } catch (InterruptedException e) {
            }
        }
    }
    
  • WAITING:線(xiàn)程在無(wú)限等待喚醒。

    public static void waiting() {
        final Object lock = new Object();
        synchronized (lock) {
            try {
                lock.wait();
            } catch (InterruptedException e) {
            }
        }
    }
    

11、Arthas


  • 啟動(dòng):java -jar arthas-boot.jar,之后選擇進(jìn)程:1
  • 查看JVM詳細(xì)配置情況: jvm,類(lèi)似 JAVA 中jinfo 命令。
  • 實(shí)時(shí)數(shù)據(jù)面板:dashboard。
  • 查看線(xiàn)程棧:thread 1。支持管道,thread 1 | rep 'main(' 可以查找到 main class
  • 導(dǎo)出堆文件:heapdump [filepath] 生產(chǎn)環(huán)境慎用,影響很大。
  • 查找類(lèi):sc -d *MathGame。
  • 反編譯:jad demo.Test。
  • 監(jiān)控函數(shù)參數(shù)/返回值/異常信息:watch demo.MathGame primeFactors "{params,target,returnObj}" [-x 2] [-b] [-s] [-n 2]。
  • 退出:臨時(shí)退出(可再次連接):exitquit;徹底退出:stop
  • 熱替換:redefine /path/Test.class 目前有限制條件:只能改方法實(shí)現(xiàn),不能改方法名,不能改屬性。

12、常用參數(shù)


參數(shù)分類(lèi)

  • 標(biāo)準(zhǔn):- 開(kāi)頭,所有 HotSpot 都支持。
  • 非標(biāo)準(zhǔn):-X 開(kāi)頭,特定版本 HotSpot 支持。通過(guò) java -X 查看。
  • 不穩(wěn)定:-XX 開(kāi)頭,下個(gè)版本可能取消。通過(guò) java -XX:+PrintFlagsFinal -version 查看。

常用GC參數(shù)組合(1.8版)

Linux 中沒(méi)找到默認(rèn) GC 的查看方法,而 Windows 中會(huì)打印 UseParallelGC。
可通過(guò)-XX:+PrintCommandLineFlags -version(僅在 Windows 有打印) 或通過(guò) GC 日志來(lái)分辨。

1.8.0_222 默認(rèn) PS + PO。

  • -XX:+UseSerialGC:Serial New(DefNew) + Serial Old

    適用于小型程序。

  • -XX:+UseParNewGC:ParNew + SerialOld

    此組合很少適用(某些版本已廢棄)。

  • -XX:+UseConcMarkSweepGC: ParNew + CMS + SerialOld

  • -XX:+UseParallelGC:Parallel Scavenge + Parallel Old

    1.8 版本默認(rèn)配置。

  • -XX:+UseParallelOldGC:Parallel Scavenge + Parallel Old

  • -XX:+UseG1GC:G1

JVM常用參數(shù)

  • -Xmn 年輕代大小。
  • -Xms 最小堆大小。
  • -Xmx 最大堆大小。
  • -Xss ??臻g大小。
  • -XX:MaxMetaspaceSize:方法區(qū)大小。
  • -XX:+PrintVMOptions 打印JVM啟動(dòng)參數(shù)。
  • -XX:+PrintFlagsFinal 打印JVM參數(shù)。
  • -XX:+PrintFlagsInitial 初始化默認(rèn)參數(shù)。
  • -verbose:class 類(lèi)加載詳細(xì)過(guò)程。
  • -XX:-DisableExplicitGC 屏蔽 System.gc() 顯式調(diào)用。
  • -XX:MaxTenuringThreshold 升代年齡,最大值15。
  • -XX:+HeapDumpOnOutOfMemoryError:OOM 時(shí),自動(dòng) Memory Dump。
  • -XX:+UseTLAB 使用TLAB,默認(rèn)打開(kāi),不建議調(diào)整。
  • -XX:+PrintTLAB 打印TLAB的使用情況,不建議調(diào)整。
  • -XX:TLABSize 設(shè)置TLAB大小,不建議調(diào)整。
  • -XX:PreBlockSpin 鎖自旋次數(shù),不建議調(diào)整。
  • -XX:CompileThreshold 熱點(diǎn)代碼檢測(cè)參數(shù),不建議調(diào)整。

GC日志參數(shù)

  • -Xloggc:/path/logs/gc.log 日志文件目錄。
  • -XX:+PrintGC:打印GC信息。
  • -XX:+PrintGCDetails:打印詳細(xì)GC信息。
  • -XX:+PrintGCCause:打印GC產(chǎn)生的原因。
  • -XX:+PrintHeapAtGC GC時(shí)打印堆棧情況。
  • -XX:+PrintGCTimeStamps:打印GC產(chǎn)生時(shí)詳細(xì)系統(tǒng)時(shí)間。
  • -XX:+PrintGCDateStamps:打印GC產(chǎn)生的日期+時(shí)間。
  • -XX:+PrintGCApplicationConcurrentTime (低)打印應(yīng)用程序時(shí)間。
  • -XX:+PrintGCApplicationStoppedTime (低)打印應(yīng)用程序暫停時(shí)長(zhǎng)。
  • -XX:+PrintReferenceGC (低)記錄回收了多少種不同引用類(lèi)型的引用。

Parallel常用參數(shù)

  • -XX:SurvivorRatio 幸存區(qū)比例。
  • -XX:PreTenureSizeThreshold 大對(duì)象體積。
  • -XX:MaxTenuringThreshold 生代年齡,最大15。
  • -XX:+ParallelGCThreads 并行收集器的線(xiàn)程數(shù),同樣適用于CMS,一般設(shè)為和CPU核數(shù)相同。
  • -XX:+UseAdaptiveSizePolicy 自動(dòng)選擇各區(qū)大小比例。

CMS常用參數(shù)

  • -XX:+UseConcMarkSweepGC 指定 GC 為 CMS。
  • -XX:ParallelCMSThreads CMS 線(xiàn)程數(shù)量。
  • -XX:CMSInitiatingOccupancyFraction 老年代使用多少比例后,啟動(dòng)CMS,默認(rèn) 68%(近似值)。
  • -XX:+UseCMSCompactAtFullCollection 在 FGC 時(shí)進(jìn)行壓縮。解決
  • -XX:CMSFullGCsBeforeCompaction 多少次 FGC 后進(jìn)行壓縮。
  • -XX:+CMSClassUnloadingEnabled 回收永久代。
  • -XX:CMSInitiatingPermOccupancyFraction 達(dá)到多少比例時(shí),進(jìn)行 Perm 回收。
  • -XX:GCTimeRatio 設(shè)置 GC 時(shí)間占應(yīng)用程序運(yùn)行時(shí)間的百分比。
  • -XX:MaxGCPauseMillis 停頓時(shí)間,是一個(gè)建議值,GC 會(huì)嘗試用各種手段達(dá)到這個(gè)時(shí)間,比如減小年輕代。

G1常用參數(shù)

  • -XX:+UseG1GC 指定 GC 為 G1。

  • -XX:MaxGCPauseMillis 建議值,G1會(huì)嘗試調(diào)整 Young 區(qū)的塊數(shù)來(lái)達(dá)到這個(gè)值。

  • -XX:GCPauseIntervalMillis GC的間隔時(shí)間。

  • -XX:+G1HeapRegionSize 分區(qū)大小,建議逐漸增大該值,1 2 4 8 16 32。
    隨著size增加,垃圾的存活時(shí)間更長(zhǎng),GC間隔更長(zhǎng),但每次GC的時(shí)間也會(huì)更長(zhǎng)。ZGC做了改進(jìn)(動(dòng)態(tài)區(qū)塊大?。?/p>

  • -XX:G1NewSizePercent 新生代最小比例,默認(rèn)為5%。

  • -XX:G1MaxNewSizePercent 新生代最大比例,默認(rèn)為60%。

  • -XX:GCTimeRatio GC時(shí)間建議比例,G1會(huì)根據(jù)這個(gè)值調(diào)整堆空間。

  • -XX:ConcGCThreads 線(xiàn)程數(shù)量。

  • -XX:InitiatingHeapOccupancyPercent 啟動(dòng)G1的堆空間占用比例。

13、案例


  • 案例1:垂直電商,最高每日百萬(wàn)訂單,處理訂單系統(tǒng)需要什么樣的服務(wù)器配置?
    這個(gè)問(wèn)題比較業(yè)余,因?yàn)楹芏嗖煌姆?wù)器配置都能支撐。
    找到最巔峰的瞬間,例如某個(gè)小時(shí)內(nèi)產(chǎn)生40萬(wàn)訂單,做到能夠支撐平均 100訂單/秒即可。
    關(guān)于內(nèi)存,可以計(jì)算一個(gè)訂單產(chǎn)生需要多少內(nèi)存。一般 512K 就能存儲(chǔ)很多數(shù)據(jù)了。
    專(zhuān)業(yè)的問(wèn)法:要求相應(yīng)時(shí)間 100ms。
    壓測(cè)!最簡(jiǎn)單的是加機(jī)器。
  • 案例2:12306遭遇春節(jié)大規(guī)模搶票應(yīng)該如何支撐?
    12306應(yīng)該是中國(guó)并發(fā)量最大的秒殺網(wǎng)站,號(hào)稱(chēng)并發(fā)量 100W 最高。
    CDN -> LVS -> NGINX -> 業(yè)務(wù)系統(tǒng) -> 每臺(tái)機(jī)器1W并發(fā)<font color="#F00">(單機(jī)10K問(wèn)題)</font> 100臺(tái)機(jī)器
    普通電商訂單 -> 下單 -> 訂單系統(tǒng)(IO)減庫(kù)存 -> 等待用戶(hù)付款
    12306的一種可能的模型:下單 -> 減庫(kù)存和訂單(redis kafka)同時(shí)異步進(jìn)行 -> 等待付款
    減庫(kù)存最后還會(huì)把壓力壓到一臺(tái)服務(wù)器
    可以做分布式本地庫(kù)存 + 單獨(dú)服務(wù)器做庫(kù)存均衡
  • 案例3:怎么得到一個(gè)事務(wù)會(huì)消耗多少內(nèi)存?

    1. 弄臺(tái)機(jī)器,看能承受多少TPS?是不是達(dá)到目標(biāo)?擴(kuò)容或調(diào)優(yōu),讓它達(dá)到。
    2. 用壓測(cè)來(lái)確定。
  • 案例4:硬件升級(jí),系統(tǒng)反而卡頓。

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

    原網(wǎng)站由于內(nèi)存較低,很多數(shù)據(jù) load 到內(nèi)存,內(nèi)存不足,會(huì)頻繁GC,響應(yīng)時(shí)間變慢。

    升級(jí)后,內(nèi)存擴(kuò)大了,但 GC 沒(méi)有調(diào)整,GC 和 YGC 頻率變低了,但 STW 時(shí)間更長(zhǎng)了。

    可以更換 PS + PO 為 PN + CMS 或 G1。

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

    CPU 100% 一定有線(xiàn)程在占用系統(tǒng)資源:

    1. top 命令找出哪個(gè)進(jìn)程 CPU 高。
    2. top -Hp 命令找出該進(jìn)程中哪個(gè)線(xiàn)程 CPU 高。
    3. jstack 命令導(dǎo)出該線(xiàn)程的堆棧。
    4. jstack 命令查找哪個(gè)方法(棧幀)消耗時(shí)間。
    5. 需要確定是工作線(xiàn)程,還是GC線(xiàn)程占比高。
  • 案例6:系統(tǒng)內(nèi)存飆高,如何查找問(wèn)題?

    內(nèi)存飚高,一定是堆內(nèi)存占用比較多:

    1. jmap 導(dǎo)出堆內(nèi)存。
    2. jhat jvisualvm mat jprofiler 等工具分析。
  • 案例7:JIRA 問(wèn)題 - 全球協(xié)同辦公,多地在使用的線(xiàn)上系統(tǒng),系統(tǒng)不停的 FGC,使用十分卡頓,但是能用,實(shí)在用不了的情況下重啟。啟動(dòng)參數(shù):

    _> /opt/atlassian/jira/jre/bin/java \
    _>     -Djava.util.logging.config.file=/opt/atlassian/jira/conf/logging.properties \
    _>     -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \
    _>     -Xms1024m \
    _>     -Xmx9216m \
    _>     -Djava.awt.headless=true \
    _>     -Datlassian.standalone=JIRA \
    _>     -Dorg.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER=true \
    _>     -Dmail.mime.decodeparameters=true \
    _>     -Dorg.dom4j.factory=com.atlassian.core.xml.InterningDocumentFactory \
    _>     -XX:-OmitStackTraceInFastThrow \
    _>     -Datlassian.plugins.startup.options= \
    _>     -Djdk.ephemeralDHKeySize=2048 \
    _>     -Djava.protocol.handler.pkgs=org.apache.catalina.webresources \
    _>     -Xloggc:/opt/atlassian/jira/logs/atlassian-jira-gc-%t.log \
    _>     -XX:+UseGCLogFileRotation \
    _>     -XX:NumberOfGCLogFiles=5 \
    _>     -XX:GCLogFileSize=20M \
    _>     -XX:+PrintGCDetails \
    _>     -XX:+PrintGCDateStamps \
    _>     -XX:+PrintGCTimeStamps \
    _>     -XX:+PrintGCCause \
    _>     -classpath /opt/atlassian/jira/bin/bootstrap.jar:/opt/atlassian/jira/bin/tomcat-juli.jar \
    _>     -Dcatalina.base=/opt/atlassian/jira \
    _>     -Dcatalina.home=/opt/atlassina/jira \
    _>     -Djava.io.tmpdir=/opt/atlassina/jira/temp \
    _>     org.apache.catalina.startup.Bootstrap start
    

    解決過(guò)程:

    1. 調(diào)整堆內(nèi)存 -Xms9216M -Xmx9216M,阻止彈性擴(kuò)容縮。
    2. 由于不能再生產(chǎn)使用 jmap, 增加 -XX:+HeapDumpOnOutOfMemoryError 參數(shù),宕機(jī)時(shí)導(dǎo)出堆。
    3. 將 JVM 內(nèi)存調(diào)整到64G,調(diào)整 GC 為 G1,之后運(yùn)行一個(gè)月沒(méi)有出現(xiàn)卡頓,運(yùn)行正常。
    4. 直到最后問(wèn)題解決,也沒(méi)有找到原因。
  • 案例8:Lambda 表達(dá)式導(dǎo)致方法區(qū)溢出問(wèn)題

    Lambda 表達(dá)式會(huì)對(duì)每一個(gè)對(duì)象實(shí)例產(chǎn)生內(nèi)部類(lèi)(新的 class),GC 回收不過(guò)來(lái),最終拋出 java.lang.OutOfMemoryError: Compressed class space 異常。

    方法區(qū)的清理,每個(gè)GC不同,有些GC不會(huì)清理,有些GC會(huì)清,但條件很苛刻(不存在該 class 對(duì)象)。這件事情很少發(fā)生。

  • 案例9:重寫(xiě) finalize 引發(fā)頻繁GC

    小米云,HBase 同步系統(tǒng),系統(tǒng)通過(guò) nginx 訪(fǎng)問(wèn)超時(shí)報(bào)警,最后排查,C++ 程序員重寫(xiě) finalize 引發(fā)頻繁 GC 問(wèn)題。

    為什么 C++ 程序員會(huì)重寫(xiě) finalize?C++ 語(yǔ)言中,需要手動(dòng)回收內(nèi)存,new 調(diào)用構(gòu)造函數(shù)開(kāi)辟內(nèi)存,delete 調(diào)用析構(gòu)函數(shù)回收內(nèi)存。

    由于重寫(xiě)了 finalize 函數(shù),每次回收?qǐng)?zhí)行大量邏輯代碼,耗時(shí)較長(zhǎng),導(dǎo)致 GC 回收不過(guò)來(lái)。

  • 案例10:Disruptor OOM 問(wèn)題

    Disruptor 可以設(shè)置鏈的長(zhǎng)度,如果過(guò)大,且對(duì)象很大,消費(fèi)完不主動(dòng)釋放,會(huì)產(chǎn)生溢出。

  • 案例11:內(nèi)存一直消耗不超過(guò)10%,F(xiàn)GC 總是頻繁發(fā)生。

    手動(dòng)調(diào)用了 System.gc()。

    可以設(shè)置 JVM 參數(shù) -XX:-DisableExplicitGC 屏蔽 GC 顯式調(diào)用。

附錄:常用命令


  • 查看非標(biāo)參數(shù):java -X

  • 查看不穩(wěn)定參數(shù):java -XX:+PrintFlagsFinal -version

  • 打印啟動(dòng)參數(shù):-XX:+PrintCommandLineFlags -version Windows 包含 GC。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容