3:類加載-初始化
1. 加載過程
1. Loading
1. 雙親委派,主要出于安全來考慮
2. LazyLoading 五種情況
1. –new getstatic putstatic invokestatic指令,訪問final變量除外
–java.lang.reflect對類進行反射調用時
–初始化子類的時候,父類首先初始化
–虛擬機啟動時,被執行的主類必須初始化
–動態語言支持java.lang.invoke.MethodHandle解析的結果為REF_getstatic
REF_putstatic REF_invokestatic的方法句柄時,該類必須初始化
3. ClassLoader的源碼
1. findInCache -> parent.loadClass -> findClass()
4. 自定義類加載器
1. extends ClassLoader
2. overwrite findClass() -> defineClass(byte[] -> Class clazz)
3. 加密
4. 第一節課遺留問題:parent是如何指定的,打破雙親委派,學生問題桌面圖片
1. 用super(parent)指定
2. 雙親委派的打破
1. 如何打破:重寫loadClass()
2. 何時打破過?
1. JDK1.2之前,自定義ClassLoader都必須重寫loadClass()
2. ThreadContextClassLoader可以實現基礎類調用實現類代碼,通
過thread.setContextClassLoader指定
3. 熱啟動,熱部署
1. osgi tomcat 都有自己的模塊指定classloader(可以加載同
一類庫的不同版本)
5. 混合執行 編譯執行 解釋執行
1. 檢測熱點代碼:-XX:CompileThreshold = 10000
2. Linking
1. Verification
1. 驗證文件是否符合JVM規定
2. Preparation
1. 靜態成員變量賦默認值
3. Resolution
1. 將類、方法、屬性等符號引用解析為直接引用
常量池中的各種符號引用解析為指針、偏移量等內存地址的直接引用
3. Initializing
1. 調用類初始化代碼 ,給靜態成員變量賦初始值
2. 小總結:
1. load - 默認值 - 初始值
2. new - 申請內存 - 默認值 - 初始值
JMM
硬件層數據一致性
協議很多
intel 用MESI
https://www.cnblogs.com/z00377750/p/9180644.html
現代CPU的數據一致性實現 = 緩存鎖(MESI ...) + 總線鎖
讀取緩存以cache line為基本單位,目前64bytes
位于同一緩存行的兩個不同數據,被兩個不同CPU鎖定,產生互相影響的偽共享問題
偽共享問題:JUC/c_028_FalseSharing
使用緩存行的對齊能夠提高效率
亂序問題
CPU為了提高指令執行效率,會在一條指令執行過程中(比如去內存讀數據(慢100倍)),去同時執
行另一條指令,前提是,兩條指令沒有依賴關系
https://www.cnblogs.com/liushaodong/p/4777308.html
寫操作也可以進行合并
https://www.cnblogs.com/liushaodong/p/4777308.html
JUC/029_WriteCombining
亂序執行的證明:JVM/jmm/Disorder.java
原始參考:https://preshing.com/20120515/memory-reordering-caught-in-the-act/
如何保證特定情況下不亂序
硬件內存屏障 X86
sfence: store| 在sfence指令前的寫操作當必須在sfence指令后的寫操作前完成。
lfence:load | 在lfence指令前的讀操作當必須在lfence指令后的讀操作前完成。
mfence:modify/mix | 在mfence指令前的讀寫操作當必須在mfence指令后的讀寫操作前完
成。
原子指令,如x86上的”lock …” 指令是一個Full Barrier,執行時會鎖住內存子系統來確保執行順
序,甚至跨多個CPU。Software Locks通常使用了內存屏障或原子指令來實現變量可見性和保持
程序順序
JVM級別如何規范(JSR133)
LoadLoad屏障:
對于這樣的語句Load1; LoadLoad; Load2,
在Load2及后續讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。
StoreStore屏障:
LoadStore屏障:
StoreLoad屏障:
對于這樣的語句Store1; StoreLoad; Load2,
在Load2及后續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。
volatile的實現細節
1. 字節碼層面
ACC_VOLATILE
2. JVM層面
volatile內存區的讀寫 都加屏障
StoreStoreBarrier
volatile 寫操作
StoreLoadBarrier
LoadLoadBarrier
volatile 讀操作
LoadStoreBarrier
3. OS和硬件層面
https://blog.csdn.net/qq_26222859/article/details/52235930
hsdis - HotSpot Dis Assembler
windows lock 指令實現 | MESI實現
synchronized實現細節
1. 字節碼層面
ACC_SYNCHRONIZED
monitorenter monitorexit
2. JVM層面
C C++ 調用了操作系統提供的同步機制
3. OS和硬件層面
X86 : lock cmpxchg / xxx
https://blog.csdn.net/21aspnet/article/details/88571740
對于這樣的語句Store1; StoreStore; Store2,
在Store2及后續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
對于這樣的語句Load1; LoadStore; Store2,
在Store2及后續寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。
使用JavaAgent測試Object的大小
作者:馬士兵 http://www.mashibing.com
對象大小(64位機)
觀察虛擬機配置
java -XX:+PrintCommandLineFlags -version
普通對象
1. 對象頭:markword 8
2. ClassPointer指針:-XX:+UseCompressedClassPointers 為4字節 不開啟為8字節
3. 實例數據
1. 引用類型:-XX:+UseCompressedOops 為4字節 不開啟為8字節
Oops Ordinary Object Pointers
4. Padding對齊,8的倍數
數組對象
1. 對象頭:markword 8
2. ClassPointer指針同上
3. 數組長度:4字節
4. 數組數據
5. 對齊 8的倍數
實驗
1. 新建項目ObjectSize (1.8)
2. 創建文件ObjectSizeAgent
3. src目錄下創建META-INF/MANIFEST.MF
package com.mashibing.jvm.agent;
import java.lang.instrument.Instrumentation;
public class ObjectSizeAgent {
private static Instrumentation inst;
public static void premain(String agentArgs, Instrumentation _inst) {
inst = _inst;
}
public static long sizeOf(Object o) {
return inst.getObjectSize(o);
}
}
注意Premain-Class這行必須是新的一行(回車 + 換行),確認idea不能有任何錯誤提示
4. 打包jar文件
5. 在需要使用該Agent Jar的項目中引入該Jar包
project structure - project settings - library 添加該jar包
6. 運行時需要該Agent Jar的類,加入參數:
7. 如何使用該類:
Hotspot開啟內存壓縮的規則(64位機)
1. 4G以下,直接砍掉高32位
2. 4G - 32G,默認開啟內存壓縮 ClassPointers Oops
3. 32G,壓縮無效,使用64位
內存并不是越大越好(^-^)
IdentityHashCode的問題
Manifest-Version: 1.0
Created-By: mashibing.com
Premain-Class: com.mashibing.jvm.agent.ObjectSizeAgent
-
javaagent:C:\work\ijprojects\ObjectSize\out\artifacts\ObjectSize_jar\ObjectS
ize.jar
```java
package com.mashibing.jvm.c3_jmm;
import com.mashibing.jvm.agent.ObjectSizeAgent;
public class T03_SizeOfAnObject {
public static void main(String[] args) {
System.out.println(ObjectSizeAgent.sizeOf(new Object()));
System.out.println(ObjectSizeAgent.sizeOf(new int[] {}));
System.out.println(ObjectSizeAgent.sizeOf(new P()));
}
private static class P {
//8 _markword
//4 _oop指針
int id; //4
String name; //4
int age; //4
byte b1; //1
byte b2; //1
Object o; //4
byte b3; //1
}
}
回答白馬非馬的問題:
當一個對象計算過identityHashCode之后,不能進入偏向鎖狀態
https://cloud.tencent.com/developer/article/1480590
https://cloud.tencent.com/developer/article/1484167
https://cloud.tencent.com/developer/article/1485795
https://cloud.tencent.com/developer/article/1482500
對象定位
?https://blog.csdn.net/clover_lily/article/details/80095580
1. 句柄池
2. 直接指針
Runtime Data Area and Instruction Set
jvms 2.4 2.5
指令集分類
1. 基于寄存器的指令集
2. 基于棧的指令集
Hotspot中的Local Variable Table = JVM中的寄存器
Runtime Data Area
PC 程序計數器
存放指令位置
虛擬機的運行,類似于這樣的循環:
while( not end ) {
取PC中的位置,找到對應位置的指令;
執行該指令;
PC ++;
}
JVM Stack
1. Frame - 每個方法對應一個棧幀
1. Local Variable Table
2. Operand Stack
對于long的處理(store and load),多數虛擬機的實現都是原子的
jls 17.7,沒必要加volatile
3. Dynamic Linking
https://blog.csdn.net/qq_41813060/article/details/88379473
jvms 2.6.3
4. return address
a() -> b(),方法a調用了方法b, b方法的返回值放在什么地方
Heap
Method Area
1. Perm Space (<1.8)
字符串常量位于PermSpace
FGC不會清理
大小啟動的時候指定,不能變
2. Meta Space (>=1.8)
字符串常量位于堆
會觸發FGC清理
不設定的話,最大就是物理內存
Runtime Constant Pool
Native Method Stack
Direct Memory
JVM可以直接訪問的內核空間的內存 (OS 管理的內存)
NIO , 提高效率,實現zero copy
思考:
如何證明1.7字符串常量位于Perm,而1.8位于Heap?
提示:結合GC, 一直創建字符串常量,觀察堆,和Metaspace
常用指令
store
load
pop
mul
sub
invoke
1. InvokeStatic
2. InvokeVirtual
3. InvokeInterface
4. InovkeSpecial
可以直接定位,不需要多態的方法
private 方法 , 構造方法
5. InvokeDynamic
JVM最難的指令
lambda表達式或者反射或者其他動態語言scala kotlin,或者CGLib ASM,動態產生的class,會用
到的指令
GC和GC Tuning
作者:馬士兵教育 http://mashibing.com
GC的基礎知識
1.什么是垃圾
C語言申請內存:malloc free
C++: new delete
c/C++ 手動回收內存
Java: new ?
自動內存回收,編程上簡單,系統不容易出錯,手動釋放內存,容易出兩種類型的問題:
1. 忘記回收
2. 多次回收
沒有任何引用指向的一個對象或者多個對象(循環引用)
2.如何定位垃圾
1. 引用計數(ReferenceCount)
2. 根可達算法(RootSearching)
3.常見的垃圾回收算法
1. 標記清除(mark sweep) - 位置不連續 產生碎片 效率偏低(兩遍掃描)
2. 拷貝算法 (copying) - 沒有碎片,浪費空間
3. 標記壓縮(mark compact) - 沒有碎片,效率偏低(兩遍掃描,指針需要調整)
4.JVM內存分代模型(用于分代垃圾回收算法)
1. 部分垃圾回收器使用的模型
除Epsilon ZGC Shenandoah之外的GC都是使用邏輯分代模型
G1是邏輯分代,物理不分代
除此之外不僅邏輯分代,而且物理分代
2. 新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元數據區(1.8) Metaspace
1. 永久代 元數據 - Class
2. 永久代必須指定大小限制 ,元數據可以設置,也可以不設置,無上限(受限于物理內存)
3. 字符串常量 1.7 - 永久代,1.8 - 堆
4. MethodArea邏輯概念 - 永久代、元數據
3. 新生代 = Eden + 2個suvivor區
1. YGC回收之后,大多數的對象會被回收,活著的進入s0
2. 再次YGC,活著的對象eden + s0 -> s1
3. 再次YGC,eden + s1 -> s0
4. 年齡足夠 -> 老年代 (15 CMS 6)
5. s區裝不下 -> 老年代
4. 老年代
1. 頑固分子
2. 老年代滿了FGC Full GC
5. GC Tuning (Generation)
1. 盡量減少FGC
2. MinorGC = YGC
3. MajorGC = FGC
6. 對象分配過程圖
7. 動態年齡:(不重要)
http://www.lxweimin.com/p/989d3b06a49d
8. 分配擔保:(不重要)
YGC期間 survivor區空間不夠了 空間擔保直接進入老年代
參考:https://cloud.tencent.com/developer/article/1082730
5.常見的垃圾回收器
1. JDK誕生 Serial追隨 提高效率,誕生了PS,為了配合CMS,誕生了PN,CMS是1.4版本后期引入,
CMS是里程碑式的GC,它開啟了并發回收的過程,但是CMS毛病較多,因此目前任何一個JDK版
本默認是CMS
并發垃圾回收是因為無法忍受STW
2. Serial 年輕代 串行回收
3. PS 年輕代 并行回收
4. ParNew 年輕代 配合CMS的并行回收
5. SerialOld
6. ParallelOld
7. ConcurrentMarkSweep 老年代 并發的, 垃圾回收和應用程序同時運行,降低STW的時間
(200ms)
CMS問題比較多,所以現在沒有一個版本默認是CMS,只能手工指定
CMS既然是MarkSweep,就一定會有碎片化的問題,碎片到達一定程度,CMS的老年代分配對象
分配不下的時候,使用SerialOld 進行老年代回收
想象一下:
PS + PO -> 加內存 換垃圾回收器 -> PN + CMS + SerialOld(幾個小時 - 幾天的STW)
幾十個G的內存,單線程回收 -> G1 + FGC 幾十個G -> 上T內存的服務器 ZGC
算法:三色標記 + Incremental Update
8. G1(10ms)
算法:三色標記 + SATB
9. ZGC (1ms) PK C++
算法:ColoredPointers + LoadBarrier
10. Shenandoah
算法:ColoredPointers + WriteBarrier
11. Eplison
12. PS 和 PN區別的延伸閱讀:
?https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html#GUID-3D0BB91E-
9BFF-4EBB-B523-14493A860E73
13. 垃圾收集器跟內存大小的關系
1. Serial 幾十兆
2. PS 上百兆 - 幾個G
3. CMS - 20G
4. G1 - 上百G
5. ZGC - 4T - 16T(JDK13)
1.8默認的垃圾回收:PS + ParallelOld
常見垃圾回收器組合參數設定:(1.8)
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
小型程序。默認情況下不會是這種選項,HotSpot會根據計算及配置和JDK版本自動選擇收集
器
-XX:+UseParNewGC = ParNew + SerialOld
這個組合已經很少用(在某些版本中已經廢棄)
https://stackoverflow.com/questions/34962257/why-remove-support-for-parnewserialo
ld-anddefnewcms-in-the-future
-XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默認) 【PS + SerialOld】
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-XX:+UseG1GC = G1
Linux中沒找到默認GC的查看方法,而windows中會打印UseParallelGC
java +XX:+PrintCommandLineFlags -version
通過GC的日志來分辨
Linux下1.8版本默認的垃圾回收器到底是什么?
1.8.0_181 默認(看不出來)Copy MarkCompact
1.8.0_222 默認 PS + PO
JVM調優第一步,了解JVM常用命令行參數
JVM的命令行參數參考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.htm
l
HotSpot參數分類
標準: - 開頭,所有的HotSpot都支持
非標準:-X 開頭,特定版本HotSpot支持特定命令
不穩定:-XX 開頭,下個版本可能取消
java -version
java -X
試驗用程序:
1. 區分概念:內存泄漏memory leak,內存溢出out of memory
2. java -XX:+PrintCommandLineFlags HelloGC
3. java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC
PrintGCDetails PrintGCTimeStamps PrintGCCauses
4. java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC
5. java -XX:+PrintFlagsInitial 默認參數值
6. java -XX:+PrintFlagsFinal 最終參數值
7. java -XX:+PrintFlagsFinal | grep xxx 找到對應的參數
8. java -XX:+PrintFlagsFinal -version |grep GC
PS GC日志詳解
每種垃圾回收器的日志格式是不同的!
PS日志格式
import java.util.List;
import java.util.LinkedList;
public class HelloGC {
public static void main(String[] args) {
System.out.println("HelloGC!");
List list = new LinkedList();
for(;;) {
byte[] b = new byte[1024*1024];
list.add(b);
}
}
}
heap dump部分:
total = eden + 1個survivor
調優前的基礎概念:
1. 吞吐量:用戶代碼時間 /(用戶代碼執行時間 + 垃圾回收時間)
2. 響應時間:STW越短,響應時間越好
所謂調優,首先確定,追求啥?吞吐量優先,還是響應時間優先?還是在滿足一定的響應時間的情況
下,要求達到多大的吞吐量...
問題:
科學計算,吞吐量。數據挖掘,thrput。吞吐量優先的一般:(PS + PO)
響應時間:網站 GUI API (1.8 G1)
什么是調優?
eden space 5632K, 94% used
[0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
后面的內存地址指的是,起始地址,使用空間結束地址,整體空間結束
地址
1. 根據需求進行JVM規劃和預調優
2. 優化運行JVM運行環境(慢,卡頓)
3. 解決JVM運行過程中出現的各種問題(OOM)
調優,從規劃開始
調優,從業務場景開始,沒有業務場景的調優都是耍流氓
無監控(壓力測試,能看到結果),不調優
步驟:
1. 熟悉業務場景(沒有最好的垃圾回收器,只有最合適的垃圾回收器)
1. 響應時間、停頓時間 [CMS G1 ZGC] (需要給用戶作響應)
2. 吞吐量 = 用戶時間 /( 用戶時間 + GC時間) [PS]
2. 選擇回收器組合
3. 計算內存需求(經驗值 1.5G 16G)
4. 選定CPU(越高越好)
5. 設定年代大小、升級年齡
6. 設定日志參數
1. -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -
XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -
XX:+PrintGCDateStamps -XX:+PrintGCCause
2. 或者每天產生一個日志文件
7. 觀察日志情況
案例1:垂直電商,最高每日百萬訂單,處理訂單系統需要什么樣的服務器配置?
這個問題比較業余,因為很多不同的服務器配置都能支撐(1.5G 16G)
1小時360000集中時間段, 100個訂單/秒,(找一小時內的高峰期,1000訂單/秒)
經驗值,
非要計算:一個訂單產生需要多少內存?512K * 1000 500M內存
專業一點兒問法:要求響應時間100ms
壓測!
案例2:12306遭遇春節大規模搶票應該如何支撐?
12306應該是中國并發量最大的秒殺網站:
號稱并發量100W最高
CDN -> LVS -> NGINX -> 業務系統 -> 每臺機器1W并發(10K問題) 100臺機器
普通電商訂單 -> 下單 ->訂單系統(IO)減庫存 ->等待用戶付款
12306的一種可能的模型: 下單 -> 減庫存 和 訂單(redis kafka) 同時異步進行 ->等付款
減庫存最后還會把壓力壓到一臺服務器
可以做分布式本地庫存 + 單獨服務器做庫存均衡
大流量的處理方法:分而治之
怎么得到一個事務會消耗多少內存?
1. 弄臺機器,看能承受多少TPS?是不是達到目標?擴容或調優,讓它達到
2. 用壓測來確定
優化環境
1. 有一個50萬PV的資料類網站(從磁盤提取文檔到內存)原服務器32位,1.5G
的堆,用戶反饋網站比較緩慢,因此公司決定升級,新的服務器為64位,16G
的堆內存,結果用戶反饋卡頓十分嚴重,反而比以前效率更低了
1. 為什么原網站慢?
很多用戶瀏覽數據,很多數據load到內存,內存不足,頻繁GC,STW長,響應時間變慢
2. 為什么會更卡頓?
內存越大,FGC時間越長
3. 咋辦?
PS -> PN + CMS 或者 G1
2. 系統CPU經常100%,如何調優?(面試高頻)
CPU100%那么一定有線程在占用系統資源,
1. 找出哪個進程cpu高(top)
2. 該進程中的哪個線程cpu高(top -Hp)
3. 導出該線程的堆棧 (jstack)
4. 查找哪個方法(棧幀)消耗時間 (jstack)
5. 工作線程占比高 | 垃圾回收線程占比高
3. 系統內存飆高,如何查找問題?(面試高頻)
1. 導出堆內存 (jmap)
2. 分析 (jhat jvisualvm mat jprofiler ... )
4. 如何監控JVM
1. jstat jvisualvm jprofiler arthas top...
解決JVM運行中的問題
一個案例理解常用工具
1. 測試代碼:
package com.mashibing.jvm.gc;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 從數據庫中讀取信用數據,套用模型,并把結果進行記錄和傳輸
*/
public class T15_FullGC_Problem01 {
private static class CardInfo {
BigDecimal price = new BigDecimal(0.0);
String name = "張三";
int age = 5;
Date birthdate = new Date();
public void m() {}
2. java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01
3. 一般是運維團隊首先受到報警信息(CPU Memory)
4. top命令觀察到問題:內存不斷增長 CPU占用率居高不下
5. top -Hp 觀察進程中的線程,哪個線程CPU和內存占比高
6. jps定位具體java進程
jstack 定位線程狀況,重點關注:WAITING BLOCKED
eg.
waiting on <0x0000000088ca3310> (a java.lang.Object)
假如有一個進程中100個線程,很多線程都在waiting on ,一定要找到是哪個線程持有這把鎖
怎么找?搜索jstack dump的信息,找 ,看哪個線程持有這把鎖RUNNABLE
作業:1:寫一個死鎖程序,用jstack觀察 2 :寫一個程序,一個線程持有鎖不釋放,其他線程等
待
7. 為什么阿里規范里規定,線程的名稱(尤其是線程池)都要寫有意義的名稱
怎么樣自定義線程池里的線程名稱?(自定義ThreadFactory)
}
private static ScheduledThreadPoolExecutor executor = new
ScheduledThreadPoolExecutor(50,
new ThreadPoolExecutor.DiscardOldestPolicy());
public static void main(String[] args) throws Exception {
executor.setMaximumPoolSize(50);
for (;;){
modelFit();
Thread.sleep(100);
}
}
private static void modelFit(){
List<CardInfo> taskList = getAllCardInfo();
taskList.forEach(info -> {
// do something
executor.scheduleWithFixedDelay(() -> {
//do sth with info
info.m();
}, 2, 3, TimeUnit.SECONDS);
});
}
private static List<CardInfo> getAllCardInfo(){
List<CardInfo> taskList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
CardInfo ci = new CardInfo();
taskList.add(ci);
}
return taskList;
}
}
8. jinfo pid
9. jstat -gc 動態觀察gc情況 / 閱讀GC日志發現頻繁GC / arthas觀察 / jconsole/jvisualVM/
Jprofiler(最好用)
jstat -gc 4655 500 : 每個500個毫秒打印GC的情況
如果面試官問你是怎么定位OOM問題的?如果你回答用圖形界面(錯誤)
1:已經上線的系統不用圖形界面用什么?(cmdline arthas)
2:圖形界面到底用在什么地方?測試!測試的時候進行監控!(壓測觀察)
10. jmap - histo 4655 | head -20,查找有多少對象產生
11. jmap -dump:format=b,file=xxx pid :
線上系統,內存特別大,jmap執行期間會對進程產生很大影響,甚至卡頓(電商不適合)
1:設定了參數HeapDump,OOM的時候會自動產生堆轉儲文件
2:很多服務器備份(高可用),停掉這臺服務器對其他服務器不影響
3:在線定位(一般小點兒公司用不到)
12. java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError
com.mashibing.jvm.gc.T15_FullGC_Problem01
13. 使用MAT / jhat /jvisualvm 進行dump文件分析
https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
jhat -J-mx512M xxx.dump
http://192.168.17.11:7000
拉到最后:找到對應鏈接
可以使用OQL查找特定問題對象
14. 找到代碼的問題
jconsole遠程連接
1. 程序啟動加入參數:
2. 如果遭遇 Local host name unknown:XXX的錯誤,修改/etc/hosts文件,把XXX加入進去
3. 關閉linux防火墻(實戰中應該打開對應端口)
4. windows上打開 jconsole遠程連接 192.168.17.11:11111
jvisualvm遠程連接
https://www.cnblogs.com/liugh/p/7620336.html (簡單做法)
jprofiler (收費)
arthas在線排查工具
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 XXX
192.168.17.11 basic localhost localhost.localdomain localhost4
localhost4.localdomain4
::1 localhost localhost.localdomain localhost6
localhost6.localdomain6
service iptables stop
chkconfig iptables off #永久關閉
為什么需要在線排查?
在生產上我們經常會碰到一些不好排查的問題,例如線程安全問題,用最簡單的threaddump或者
heapdump不好查到問題原因。為了排查這些問題,有時我們會臨時加一些日志,比如在一些關
鍵的函數里打印出入參,然后重新打包發布,如果打了日志還是沒找到問題,繼續加日志,重新打
包發布。對于上線流程復雜而且審核比較嚴的公司,從改代碼到上線需要層層的流轉,會大大影響
問題排查的進度。
jvm觀察jvm信息
thread定位線程問題
dashboard 觀察系統情況
heapdump + jhat分析
jad反編譯
動態代理生成類的問題定位
第三方的類(觀察代碼)
版本問題(確定自己最新提交的版本是不是被使用)
redefine 熱替換
目前有些限制條件:只能改方法實現(方法已經運行完成),不能改方法名, 不能改屬性
m() -> mm()
sc - search class
watch - watch method
沒有包含的功能:jmap
GC算法的基礎概念
Card Table
由于做YGC時,需要掃描整個OLD區,效率非常低,所以JVM設計了CardTable, 如果一個OLD區
CardTable中有對象指向Y區,就將它設為Dirty,下次掃描時,只需要掃描Dirty Card
在結構上,Card Table用BitMap來實現
CMS
CMS的問題
1. Memory Fragmentation
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction 默認為0 指的是經過多少次FGC才進行壓縮
2. Floating Garbage
Concurrent Mode Failure
產生:if the concurrent collector is unable to finish reclaiming the unreachable objects
before the tenured generation fills up, or if an allocation cannot be satisfiedwith the
available free space blocks in the tenured generation, then theapplication is paused
and the collection is completed with all the applicationthreads stopped
解決方案:降低觸發CMS的閾值
PromotionFailed
解決方案類似,保持老年代有足夠的空間
–XX:CMSInitiatingOccupancyFraction 92% 可以降低這個值,讓CMS保持老年代足夠的空
間
CMS日志分析
執行命令:java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC
com.mashibing.jvm.gc.T15_FullGC_Problem01
[GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K),
0.0268035 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
ParNew:年輕代收集器
6144->640:收集前后的對比
(6144):整個年輕代容量
6585 -> 2770:整個堆的情況
(19840):整個堆大小
G1
[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) : 整個堆使用(最大)
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02
secs]
//這里的時間意義不大,因為是并發執行
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00,
real=0.00 secs]
//標記Card為Dirty,也稱為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下的存活對象標記
//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): 階段過后的老年代占用及容量
//10108K(19840K): 階段過后的堆占用及容量
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01
secs]
//標記已經完成,進行并發清理
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00
secs]
//重置內部結構,為下次GC做準備
1. ?https://www.oracle.com/technical-resources/articles/java/g1gc.html
G1日志詳解
案例匯總
OOM產生的原因多種多樣,有些程序未必產生OOM,不斷FGC(CPU飆高,但內存回收特別少) (上面
案例)
1. 硬件升級系統反而卡頓的問題(見上)
2. 線程池不當運用產生OOM問題(見上)
不斷的往List里加對象(實在太LOW)
[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]
//young -> 年輕代 Evacuation-> 復制存活對象
//initial-mark 混合回收的階段,這里是YGC混合老年代回收
[Parallel Time: 1.5 ms, GC Workers: 1] //一個GC線程
[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]
//無法evacuation,進行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]
3. smile jira問題
實際系統不斷重啟
解決問題 加內存 + 更換垃圾回收器 G1
真正問題在哪兒?不知道
4. tomcat http-header-size過大問題(Hector)
5. lambda表達式導致方法區溢出問題(MethodArea / Perm Metaspace)
LambdaGC.java -XX:MaxMetaspaceSize=9M -XX:+PrintGCDetails
"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -XX:MaxMetaspaceSize=9M -
XX:+PrintGCDetails "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA
Community Edition 2019.1\lib\idea_rt.jar=49316:C:\Program
Files\JetBrains\IntelliJ IDEA Community Edition 2019.1\bin" -
Dfile.encoding=UTF-8 -classpath "C:\Program
Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program
Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\work\ijprojects\JVM\out\production
\JVM;C:\work\ijprojects\ObjectSize\out\artifacts\ObjectSize_jar\ObjectSize.j
ar" com.mashibing.jvm.gc.LambdaGC
[GC (Metadata GC Threshold) [PSYoungGen: 11341K->1880K(38400K)] 11341K-
>1888K(125952K), 0.0022190 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Metadata GC Threshold) [PSYoungGen: 1880K->0K(38400K)] [ParOldGen:
8K->1777K(35328K)] 1888K->1777K(73728K), [Metaspace: 8164K-
>8164K(1056768K)], 0.0100681 secs] [Times: user=0.02 sys=0.00, real=0.01
secs]
[GC (Last ditch collection) [PSYoungGen: 0K->0K(38400K)] 1777K-
>1777K(73728K), 0.0005698 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Last ditch collection) [PSYoungGen: 0K->0K(38400K)] [ParOldGen:
1777K->1629K(67584K)] 1777K->1629K(105984K), [Metaspace: 8164K-
>8156K(1056768K)], 0.0124299 secs] [Times: user=0.06 sys=0.00, real=0.01
secs]
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62
)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl
.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at
sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImp
l.java:388)
at
sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(Instrumentation
Impl.java:411)
Caused by: java.lang.OutOfMemoryError: Compressed class space
at sun.misc.Unsafe.defineClass(Native Method)
at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:63)
at
sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:399)
at
sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:394)
at java.security.AccessController.doPrivileged(Native Method)
at
sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:39
3)
at
sun.reflect.MethodAccessorGenerator.generateSerializationConstructor(MethodA
ccessorGenerator.java:112)
at
sun.reflect.ReflectionFactory.generateConstructor(ReflectionFactory.java:398
)
at
sun.reflect.ReflectionFactory.newConstructorForSerialization(ReflectionFacto
ry.java:360)
at
java.io.ObjectStreamClass.getSerializableConstructor(ObjectStreamClass.java:
1574)
at java.io.ObjectStreamClass.access$1500(ObjectStreamClass.java:79)
at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:519)
at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:494)
at java.security.AccessController.doPrivileged(Native Method)
at java.io.ObjectStreamClass.<init>(ObjectStreamClass.java:494)
at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:391)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1134)
at
java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
at
java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
at
java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at
javax.management.remote.rmi.RMIConnectorServer.encodeJRMPStub(RMIConnectorSe
rver.java:727)
at
javax.management.remote.rmi.RMIConnectorServer.encodeStub(RMIConnectorServer
.java:719)
at
javax.management.remote.rmi.RMIConnectorServer.encodeStubInAddress(RMIConnec
torServer.java:690)
6. 直接內存溢出問題(少見)
《深入理解Java虛擬機》P59,使用Unsafe分配直接內存,或者使用NIO的問題
7. 棧溢出問題
-Xss設定太小
8. 比較一下這兩段程序的異同,分析哪一個是更優的寫法:
9. 重寫finalize引發頻繁GC
小米云,HBase同步系統,系統通過nginx訪問超時報警,最后排查,C++程序員重寫finalize引發
頻繁GC問題
為什么C++程序員會重寫finalize?(new delete)
finalize耗時比較長(200ms)
10. 如果有一個系統,內存一直消耗不超過10%,但是觀察GC日志,發現FGC總是頻繁產生,會是什
么引起的?
System.gc() (這個比較Low)
11. Distuptor有個可以設置鏈的長度,如果過大,然后對象大,消費完不主動釋放,會溢出 (來自 死
物風情)
12. 用jvm都會溢出,mycat用崩過,1.6.5某個臨時版本解析sql子查詢算法有問題,9個exists的聯合
sql就導致生成幾百萬的對象(來自 死物風情)
13. new 大量線程,會產生 native thread OOM,(low)應該用線程池,
解決方案:減少堆空間(太TMlow了),預留更多內存產生native thread
JVM內存占物理內存比例 50% - 80%
GC常用參數
-Xmn -Xms -Xmx -Xss
年輕代 最小堆 最大堆 棧空間
-XX:+UseTLAB
使用TLAB,默認打開
-XX:+PrintTLAB
打印TLAB的使用情況
-XX:TLABSize
設置TLAB大小
at
javax.management.remote.rmi.RMIConnectorServer.start(RMIConnectorServer.java
:439)
at
sun.management.jmxremote.ConnectorBootstrap.startLocalConnectorServer(Connec
torBootstrap.java:550)
at sun.management.Agent.startLocalManagementAgent(Agent.java:137)
Object o = null;
for(int i=0; i<100; i++) {
o = new Object();
//業務處理
}
for(int i=0; i<100; i++) {
Object o = new Object();
}
-XX:+DisableExplictGC
System.gc()不管用 ,FGC
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintHeapAtGC
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationConcurrentTime (低)
打印應用程序時間
-XX:+PrintGCApplicationStoppedTime (低)
打印暫停時長
-XX:+PrintReferenceGC (重要性低)
記錄回收了多少種不同引用類型的引用
-verbose:class
類加載詳細過程
-XX:+PrintVMOptions
-XX:+PrintFlagsFinal -XX:+PrintFlagsInitial
必須會用
-Xloggc:opt/log/gc.log
-XX:MaxTenuringThreshold
升代年齡,最大值15
鎖自旋次數 -XX:PreBlockSpin 熱點代碼檢測參數-XX:CompileThreshold 逃逸分析 標量替換 ...
這些不建議設置
Parallel常用參數
-XX:SurvivorRatio
-XX:PreTenureSizeThreshold
大對象到底多大
-XX:MaxTenuringThreshold
-XX:+ParallelGCThreads
并行收集器的線程數,同樣適用于CMS,一般設為和CPU核數相同
-XX:+UseAdaptiveSizePolicy
自動選擇各區大小比例
CMS常用參數
-XX:+UseConcMarkSweepGC
-XX:ParallelCMSThreads
CMS線程數量
-XX:CMSInitiatingOccupancyFraction
使用多少比例的老年代后開始CMS收集,默認是68%(近似值),如果頻繁發生SerialOld卡頓,應該
調小,(頻繁CMS回收)
-XX:+UseCMSCompactAtFullCollection
在FGC時進行壓縮
-XX:CMSFullGCsBeforeCompaction
多少次FGC之后進行壓縮
-XX:+CMSClassUnloadingEnabled
-XX:CMSInitiatingPermOccupancyFraction
達到什么比例時進行Perm回收
GCTimeRatio
設置GC時間占用程序運行時間的百分比
-XX:MaxGCPauseMillis
停頓時間,是一個建議時間,GC會嘗試用各種手段達到這個時間,比如減小年輕代
G1常用參數
-XX:+UseG1GC
-XX:MaxGCPauseMillis
建議值,G1會嘗試調整Young區的塊數來達到這個值
-XX:GCPauseIntervalMillis
?GC的間隔時間
-XX:+G1HeapRegionSize
分區大小,建議逐漸增大該值,1 2 4 8 16 32。
隨著size增加,垃圾的存活時間更長,GC間隔更長,但每次GC的時間也會更長
ZGC做了改進(動態區塊大小)
G1NewSizePercent
新生代最小比例,默認為5%
G1MaxNewSizePercent
新生代最大比例,默認為60%
GCTimeRatio
GC時間建議比例,G1會根據這個值調整堆空間
ConcGCThreads
線程數量
InitiatingHeapOccupancyPercent
啟動G1的堆空間占用比例
作業
1. -XX:MaxTenuringThreshold控制的是什么?
A: 對象升入老年代的年齡
B: 老年代觸發FGC時的內存垃圾比例
2. 生產環境中,傾向于將最大堆內存和最小堆內存設置為:(為什么?)
A: 相同 B:不同
3. JDK1.8默認的垃圾回收器是:
A: ParNew + CMS
B: G1
C: PS + ParallelOld
D: 以上都不是
4. 什么是響應時間優先?
5. 什么是吞吐量優先?
6. ParNew和PS的區別是什么?
7. ParNew和ParallelOld的區別是什么?(年代不同,算法不同)
8. 長時間計算的場景應該選擇:A:停頓時間 B: 吞吐量
9. 大規模電商網站應該選擇:A:停頓時間 B: 吞吐量
10. HotSpot的垃圾收集器最常用有哪些?
11. 常見的HotSpot垃圾收集器組合有哪些?
12. JDK1.7 1.8 1.9的默認垃圾回收器是什么?如何查看?
13. 所謂調優,到底是在調什么?
14. 如果采用PS + ParrallelOld組合,怎么做才能讓系統基本不產生FGC
15. 如果采用ParNew + CMS組合,怎樣做才能夠讓系統基本不產生FGC
1.加大JVM內存
2.加大Young的比例
3.提高Y-O的年齡
4.提高S區比例
5.避免代碼內存泄漏
16. G1是否分代?G1垃圾回收器會產生FGC嗎?
17. 如果G1產生FGC,你應該做什么?
1. 擴內存
2. 提高CPU性能(回收的快,業務邏輯產生對象的速度固定,垃圾回收越快,內存空間越大)
3. 降低MixedGC觸發的閾值,讓MixedGC提早發生(默認是45%)
18. 問:生產環境中能夠隨隨便便的dump嗎?
小堆影響不大,大堆會有服務暫停或卡頓(加live可以緩解),dump前會有FGC
19. 問:常見的OOM問題有哪些?
棧 堆 MethodArea 直接內存
參考資料
1. https://blogs.oracle.com/jonthecollector/our-collectors
2. https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
3. http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp
4. JVM調優參考文檔:https://docs.oracle.com/en/java/javase/13/gctuning/introduction-garbage
-collection-tuning.html#GUID-8A443184-7E07-4B71-9777-4F12947C8184
5. https://www.cnblogs.com/nxlhero/p/11660854.html 在線排查工具
6. http://www.lxweimin.com/p/507f7e0cc3a3 arthas常用命令
7. Arthas手冊:
1. 啟動arthas java -jar arthas-boot.jar
2. 綁定java進程
3. dashboard命令觀察系統整體情況
4. help 查看幫助
5. help xx 查看具體命令幫助
8. jmap命令參考: http://www.lxweimin.com/p/507f7e0cc3a3
1. jmap -heap pid
2. jmap -histo pid
3. jmap -clstats pid