2. JAVA 應用相關
2.4. Java Monitor 同步鎖相關
主要是三種 Event:
當進入同步塊,嘗試獲取鎖的時候,產生 JavaMonitorEnter Event;當調用 Object.wait() 進入等待時,會產生 JavaMonitorWait Event;當 鎖升級(另一種說法是鎖膨脹)時,產生 JavaMonitorWait Event。
下面我從網上看到的這張圖,有助於理解這三種事件:
- JavaMonitorEnter(Java Monitor Blocked):進入 Java Monitor Event。當需要進入同步代碼時(字節碼 monitorenter),會產生這個 Event。在 default.jfc 中默認爲啓用的,並且會追蹤堆棧,閾值是 20ms;採集的信息包括:開始時間,持續時間,結束時間,線程,Monitor Address,Monitor Class,之前持有這個 Monitor 的線程。
- JavaMonitorInflated(Java Monitor Inflated):發生鎖升級的時候的 Event。在 default.jfc 中默認爲啓用的,並且會追蹤堆棧,閾值是 20ms;採集的信息包括:開始時間,持續時間,結束時間,線程,Monitor Address,Monitor Class,鎖升級原因
- JavaMonitorWait(Java Monitor Wait):調用 Object.wait() 進入等待時,會產生 JavaMonitorWait Event;在 default.jfc 中默認爲啓用的,並且會追蹤堆棧,閾值是 20ms;採集的信息包括:開始時間,持續時間,結束時間,線程,Monitor Address,Monitor Class,喚醒線程Thread, 是否超時, 超時時間。
一般的在默認情況下, JavaMonitorEnter 和 JavaMonitorWait 採集到的在閾值以上的會比較多,一般不會有 JavaMonitorInflated 事件,除非發生 CPU 資源耗盡或者程序不斷 dump 導致一直處於 safepoint。
但是並不是所有的 JavaMonitorEnter 和 JavaMonitorWait Event 都是我們關心的,如何快速找到我們關心的關鍵 Event 呢?
先舉一個 JavaMonitorEnter 的例子:
從事件瀏覽器視角去看, Event 太多了,我們建一個 JavaMonitorEnter Event 的視圖:
一般的,我會按照 Monitor class 去分類看,爭用同一個對象鎖一般是同一個業務:
我們來看第一個計數最多的,點擊這個分類,在下面的列表按照持續需時間倒序,查看線程以及堆棧:
發現是因爲本地緩存更新,導致比較慢,這裏本地緩存是讀取的數據庫,讀取的數據比較多,400ms 的比較正常。
我們再來看另一個,Monitor class 爲 java.lang.Object 的:
從堆棧上看出是獲取 Lettuce 連接時候,鎖等待了320ms。查看源代碼,發現是連接初始化,導致比較慢,初始化好連接之後沒再出現了。
這裏建議,針對微服務應用,再調高閾值到 50ms。
2.5. Java Thread 相關
四個事件:
- ThreadStart:線程開始, Thread.start() 時,就會產生這個 Event 記錄
- ThreadEnd:線程結束,就會產生這個 Event 記錄
- ThreadPark:一般在 await/notify 的 await 的時候,調用 Unsafe.park() 就會產生這個 Event 記錄
- ThreadSleep:線程休眠, Thread.sleep()時,就會產生這個 Event 記錄
這些事件我們一般都不關心,Java 線程阻塞與熱點方法和 CPU 消耗等,有其他的 Event,在 default.jfc 中這四個 Event 默認都是採集的,這裏建議關閉這四個 Event 的採集
2.6. 網絡IO socket 相關
- SocketRead: 網絡讀,在 default.jfc 中,默認啓用,並且會追蹤堆棧,閾值是 20ms;採集的信息包括:開始時間,持續時間,結束時間,線程,遠程 IP,讀取字節大小,是否是流讀取的末尾,遠程 Host,遠程 Port,超時時間
- SocketWrite: 網絡寫,在 default.jfc 中,默認啓用,並且會追蹤堆棧,閾值是 20ms;採集的信息包括:開始時間,持續時間,結束時間,線程,遠程 IP,寫入字節大小,遠程 Host,遠程 Port
堆棧採集對於這種 Event 很重要,但是對於 Spring Cloud 這樣的框架,調用層次極爲複雜,可能默認採集堆棧深度(64)不夠,需要增大才能看到自己的業務代碼堆棧。但是要注意的一點是:堆棧採集深度,對於性能影響很大,以最壞的情況考慮,可以理解爲增加多少倍的堆棧深度,對性能的影響就提高多少倍。 建議對於常態化的線上監控,堆棧深度最多不超過 128.
2.7. 一些統計數據相關
- ClassLoaderStatistics: 類加載器相關統計數據,default.jfc 中默認打開,每個 DataChunk 採集一次,一般不會去關心類加載器的統計數據,建議關閉。
- ClassLoadingStatistics: 類加載相關統計數據,default.jfc 中默認打開,每秒採集一次,一般不會去關心類加載的統計數據,建議關閉。
- ExceptionStatistics:異常統計數據,default.jfc 中默認打開,每秒採集一次,一般異常通過日誌處理,也不太會關心這個統計數據,建議關閉
- JavaThreadStatistics:Java 線程數量統計數據,default.jfc 中默認打開,每秒採集一次線程數量,這個還有些參考意義,建議保留默認配置。採集的數據包括:到目前爲止累計線程數量(包括已經 stop 的),當前活動線程數量,守護線程數量,採集時間內峯值線程數量。個人感覺不用每秒採集一次,改成每分鐘即可。
- ThreadAllocationStatistics:線程分配內存大小統計,包括了線程從開始到現在一共分配的內存大小(包括已釋放的),default.jfc 中默認打開,每個 DataChunk 採集一次,參考意義不大,建議關閉
2. 虛擬機相關 Event
2.1. JVM 啓動參數 Flag 相關
JVM 啓動參數包含很多配置, 同時也可以通過 JVMTI,jcmd 命令等等動態修改這些配置, 如果我們想看這些配置以及修改的時間點,那麼可以打開這些 Event 的採集:
- BooleanFlag 與 BooleanFlagChange :布爾狀態位以及變化。對應的就是通過類似於通過
+``-
配置的哪些狀態位,例如-XX:+UseCompressedOops
就是打開壓縮對象指針 - DoubleFlag 與 DoubleFlagChange:double狀態位,例如
-XX:InitialRAMPercentage=52.0
配置初始內存堆棧佔用比例(只有在沒指定-Xmx
和-Xms
的時候有效) - IntFlag 與 IntFlagChange
- UnsignedIntFlag 與 UnsignedIntFlagChange
- LongFlag 與 LongFlagChange
- UnsignedLongFlag 與 UnsignedLongFlagChange
- StringFlag 與 StringFlagChanged
這個對性能影響是很小的,所以在系統自帶的 default.jfc 中就打開了。我這裏建議還是打開,畢竟基本所有狀態位是可以通過 jcmd 命令修改的,如果有對比需求,對比修改前還有修改後性能影響,那麼狀態位變換時間就很重要了
1.2. 類加載相關
主要包括三種 Event:
- Class Define: 類定義
- Class Load: 類加載
- Class Unload: 類卸載
這些事件我們平常開發一般不會去關心,一般之後開發框架或者定位框架問題的時候,纔會去關心類加載器相關的問題。而且這個用阿里開源的工具 Arthas
更加好用(https://alibaba.github.io/arthas/sc.html):
$ sc -d demo.MathGame
class-info demo.MathGame
code-source /private/tmp/arthas-demo.jar
name demo.MathGame
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name MathGame
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
classLoaderHash 3d4eac69
Affect(row-cnt:1) cost in 875 ms.