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

- 解釋執(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)
class 文件:是二進(jìn)制字節(jié)流。
查看 class 文件:
javap -v X.class

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ù)加載。

-
打破雙親委派:
- 重寫(xiě) loadClass() 方法。
- ThreadContextClassLoader 可以實(shí)現(xiàn)基礎(chǔ)類(lèi)調(diào)用實(shí)現(xiàn)類(lèi)代碼,通過(guò)
thread.setContextClassLoader指定。 - 場(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ò)程
- Loading:class 文件 load 到內(nèi)存。
- Linking:鏈接。
- Verification:校驗(yàn) class 是否符合 JVM 規(guī)范。
- Preparation:靜態(tài)成員變量賦默認(rèn)值,不是初值。
- Resolution:將常量池中類(lèi)、方法、屬性等符號(hào)引用解析為指針、偏移量等內(nèi)存地址的直接引用。
- Initializing:靜態(tài)變量賦初始值,調(diào)用靜態(tài)代碼塊,調(diào)用類(lèi)初始化代碼。

CompilerAPI
可以手動(dòng)直接在內(nèi)存中編譯代碼,無(wú)需生成到磁盤(pán)。
LazyLoading
嚴(yán)格講應(yīng)該叫做 LazyInitializing。
JVM 規(guī)范并沒(méi)有規(guī)定何時(shí)加載,但是嚴(yán)格規(guī)定了什么時(shí)候必須初始化。
-
new、getstatic、putstatic、invokestatic指令,訪(fǎng)問(wèn)final變量除外。getstatic:讀取靜態(tài)變量。putstatic:設(shè)置靜態(tài)變量。invokestatic:執(zhí)行靜態(tài)方法 java.lang.reflect對(duì)類(lèi)進(jìn)行反射調(diào)用時(shí)。初始化子類(lèi)時(shí),父類(lèi)首先初始化。
-
虛擬機(jī)啟動(dòng)時(shí),被執(zhí)行的主類(lèi)必須初始化。
包含
main方法的類(lèi)。 動(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è):
多次被調(diào)用的方法(方法計(jì)數(shù)器:檢測(cè)方法執(zhí)行頻率)
多次被調(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ī)則:
Thread的start()方法先行發(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ì)象:
-
對(duì)象頭:markword
- 32位 - 4字節(jié)

- 64位 - 8字節(jié)

-
ClassPointer 指針:開(kāi)啟
-XX:+UseCompressedClassPointers為4字節(jié),不開(kāi)啟為8字節(jié)。默認(rèn)開(kāi)啟。可以使用
-XX:-UseCompressedClassPointers關(guān)閉。 -
實(shí)例數(shù)據(jù):普通對(duì)象指針。開(kāi)啟
-XX:+UseCompressedOops為4字節(jié),不開(kāi)啟為8字節(jié)。默認(rèn)開(kāi)啟。Oops:Ordinary Object Pointers。
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ì)象定位
- 句柄池
- 直接指針: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。

5、運(yùn)行時(shí)數(shù)據(jù)區(qū)
Run-time Data Areas


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)
- 方法區(qū)是邏輯概念,1.7版本永久代(Perm Generation)實(shí)現(xiàn),1.8版本元數(shù)據(jù)區(qū)(Metaspace)實(shí)現(xiàn)。
- 永久代需要啟動(dòng)時(shí)指定大小,且不可更改。元數(shù)據(jù)區(qū)可以設(shè)置,也可以不設(shè)置,受限于物理內(nèi)存。
- 永久代/元數(shù)據(jù)區(qū) 存放:Class 元信息、方法編譯后信息、代碼編譯后信息、JIT 編譯信息、字節(jié)碼等。
- 字符串常量:1.7 在永久代,1.8 在堆。
- 1.8 版本 新生代/老年代 內(nèi)存比例默認(rèn) 1 : 2??赏ㄟ^(guò)參數(shù)
-XX:NewRatio=2調(diào)整。

- 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ì)象包括:
- JVM stack:線(xiàn)程棧變量
- native method stack:JNI 指針
- run-time constant pool:常量池
- static references in method area:靜態(tài)變量 - 方法區(qū)內(nèi)部靜態(tài)引用
- class
回收算法
- Mark-Sweep:標(biāo)記清除 - 存活對(duì)象比較多的情況下,效率比較高,不適合 Eden 區(qū)。需要兩次掃描,第一次標(biāo)記,第二次清除,效率偏低。容易產(chǎn)生碎片。

- Copying:拷貝 - 適用于存活對(duì)象較少的情況,例如 Eden 區(qū)。只需要掃描一次,效率提高,沒(méi)有碎片。缺點(diǎn)是空間浪費(fèi),需要移動(dòng)和復(fù)制對(duì)象,指向?qū)ο蟮囊眯枰{(diào)整。

- Mark-Compact:標(biāo)記壓縮 - 空間連續(xù),沒(méi)有碎片,不會(huì)產(chǎn)生內(nèi)存浪費(fèi)。需要掃描兩次,并移動(dòng)數(shù)據(jù),效率偏低。

7、垃圾回收器

常見(jiàn)垃圾回收器:1.8 版本默認(rèn) PS + ParallelOld。
Serial:年輕代,串行回收,單線(xiàn)程設(shè)計(jì)。單 CPU 效率最高。
SerialOld:老年代,串行回收,單線(xiàn)程設(shè)計(jì),使用 mark-sweep-compact 算法。
ParallelScavenge:年輕代,串行回收,多線(xiàn)程設(shè)計(jì)。
ParallelOld:老年代,串行回收,多線(xiàn)程設(shè)計(jì),使用 mark-compact 算法。
ParNew:年輕代,配合 CMS 的并行回收?;?PS 做了增強(qiáng),例如 CMS 某些特定階段,ParNew 會(huì)同時(shí)運(yùn)行。
-
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)啟。
G1:算法 - f + SATB。
ZGC:算法 - ColloredPointers + 寫(xiě)屏障。
Shenandoah:算法 - ColloredPointers + 讀屏障。
Eplison:一般調(diào)試時(shí)使用。
GC 和內(nèi)存大小的關(guān)系:
- Serial:100Mb 以?xún)?nèi)
- PS:100Mb - 幾個(gè)Gb
- CMS:20Gb
- G1:上百Gb
- ZGC:4Tb
CMS

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

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)問(wèn)題有兩種方案:
Incremental Update - 增量更新,關(guān)注引用的增加,把黑色重新標(biāo)記為灰色,下次重新掃描屬性。CMS 使用此方案。
-
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ò)展為 ,16T,因?yàn)镃PU地址總線(xiàn)最大支持48位。
Marked0、Marked1、Remapped、Finalizable 是互斥的,同時(shí)只能有一位為1,普通對(duì)象全部為0。

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)思路
- 根據(jù)需求進(jìn)行 JVM 規(guī)劃和預(yù)調(diào)優(yōu)。
- 優(yōu)化運(yùn)行 JVM 運(yùn)行環(huán)境。
- 解決 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)。
-
步驟:
-
熟悉業(yè)務(wù)場(chǎng)景(沒(méi)有最好的垃圾回收器,只有最合適的垃圾回收器)。
確定追求吞吐量,還是響應(yīng)時(shí)間。
選擇回收器組合。
計(jì)算內(nèi)存需求(很難計(jì)算,經(jīng)驗(yàn)值,或經(jīng)過(guò)測(cè)試和監(jiān)控確定)。
選定 CPU(越高越好)。
設(shè)定年代大小、升代年齡。
-
設(shè)定日志參數(shù):
-Xloggc:/opt/xxx-xxx-gc-%t.log -XX:+useGCLogFileRotation -XX:NumberOfGCLogFiles=5-XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause 觀(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)型
- 堆溢出:
java.lang.OutOfMemoryError: Java heap space。 - 棧溢出:
-Xss設(shè)定太小,java.lang.StackOverflowError。 - 方法區(qū)溢出:
java.lang.OutOfMemoryError: Compressed class space。 - 直接內(nèi)存溢出:使用Unsafe分配直接內(nèi)存,或者使用NIO的問(wèn)題。
排查思路
top命令觀(guān)察到問(wèn)題:內(nèi)存不斷增長(zhǎng),CPU占用率居高不下。top -Hp pid命令觀(guān)察進(jìn)程中的線(xiàn)程,哪個(gè)線(xiàn)程CPU和內(nèi)存占比高。printf %x pid將10進(jìn)制PID轉(zhuǎn)換為16進(jìn)制。-
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)程持有此鎖。
jps定位具體java進(jìn)程。jinfo pid查看進(jìn)程JVM詳細(xì)信息。-
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)察。
-
jmap -histo pid | head -20查找有多少對(duì)象產(chǎn)生。此命令對(duì)在線(xiàn)系統(tǒng)影響不是很高。此步驟很關(guān)鍵,數(shù)量很多的對(duì)象,往往是造成 Heap OOM 的問(wèn)題所在。
Arthas 目前未提供此功能。
-
jmap -dump:format=b,file=xxx pid / jmap -histo無(wú)論進(jìn)程是否卡頓了,只要進(jìn)程在,就可以導(dǎo)出。執(zhí)行此命令,對(duì)在線(xiàn)系統(tǒng)的影響特別高。
java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError -jar xxx.jarHeap OOM 時(shí),自動(dòng)產(chǎn)生堆轉(zhuǎn)儲(chǔ)文件。-
使用 MAT / jhat 進(jìn)行 dump 文件分析。
jhat -J-Xmx512M heap.hprof默認(rèn)開(kāi)啟 http 7000 端口,可在瀏覽器查看分析結(jié)果。 找到代碼的問(wèn)題。
jconsole 遠(yuǎn)程連接
-
程序啟動(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 -
如果遇到
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í)退出(可再次連接):
exit或quit;徹底退出: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 Old1.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:+PrintHeapAtGCGC時(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:ParallelCMSThreadsCMS 線(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:GCPauseIntervalMillisGC的間隔時(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:GCTimeRatioGC時(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)存?
- 弄臺(tái)機(jī)器,看能承受多少TPS?是不是達(dá)到目標(biāo)?擴(kuò)容或調(diào)優(yōu),讓它達(dá)到。
- 用壓測(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)資源:
-
top命令找出哪個(gè)進(jìn)程 CPU 高。 -
top -Hp命令找出該進(jìn)程中哪個(gè)線(xiàn)程 CPU 高。 -
jstack命令導(dǎo)出該線(xiàn)程的堆棧。 -
jstack命令查找哪個(gè)方法(棧幀)消耗時(shí)間。 - 需要確定是工作線(xiàn)程,還是GC線(xiàn)程占比高。
-
-
案例6:系統(tǒng)內(nèi)存飆高,如何查找問(wèn)題?
內(nèi)存飚高,一定是堆內(nèi)存占用比較多:
-
jmap導(dǎo)出堆內(nèi)存。 -
jhatjvisualvmmatjprofiler等工具分析。
-
-
案例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ò)程:
- 調(diào)整堆內(nèi)存
-Xms9216M -Xmx9216M,阻止彈性擴(kuò)容縮。 - 由于不能再生產(chǎn)使用
jmap, 增加-XX:+HeapDumpOnOutOfMemoryError參數(shù),宕機(jī)時(shí)導(dǎo)出堆。 - 將 JVM 內(nèi)存調(diào)整到64G,調(diào)整 GC 為 G1,之后運(yùn)行一個(gè)月沒(méi)有出現(xiàn)卡頓,運(yùn)行正常。
- 直到最后問(wèn)題解決,也沒(méi)有找到原因。
- 調(diào)整堆內(nè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 -versionWindows 包含 GC。