定位CPU的問題一般可以分為以下幾個步驟:
1. 定位進程
2. 定位線程
3. 查看線程信息
4. 定位具體方法(代碼)
### 定位進程
通過`top -c`(然后按`P`按cpu排序),`htop`等工具定位到具體的高CPU進程
假設定位到的進程ID為14279
或者使用jps查看java進程號
### 定位線程
`top -H -p 14279`定位占cpu的線程:
```
%Cpu(s): 0.5 us, 0.7 sy, 0.0 ni, 98.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8168236 total, 231696 free, 3660496 used, 4276044 buff/cache
KiB Swap: 969964 total, 969964 free, 0 used. 4197860 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
14293 faceless 20 0 4508772 97036 18112 S 45 1.2 152:35.42 java
14279 faceless 20 0 4508772 97036 18112 S 23 1.2 0:00.00 java
14282 faceless 20 0 4508772 97036 18112 S 0.0 1.2 0:00.37 java
```
一般超過80%就是比較高的,80%左右是合理情況
### 查看線程信息
方法一:手動定位
使用jstack 分別找也上面的線程的具體內容,比如第一個線程 14293:
```
# 將線程ID轉換為16進制
```
printf '0x%x' 14293 => 0x37d5
```
# 通過jstack查看進程中該線程的信息:
`jstack pid | grep tid`找到線程堆棧
jstack 12816 | grep 0x3211 -A 30
# 【示例輸出】
"VM Periodic Task Thread" os_prio=0 tid=0x00007ff1802d5800 nid=0x37d5 waiting on condition
```
當然這里也可以直接使用`jstack 14279 > ~/tmp/pid-14279.log`顯示所有線程,然后手動尋找對應的ID
當然更常見的是我們對整個jstack文件進行分析,通常我們會比較關注WAITING和TIMED\_WAITING的部分,BLOCKED就不用說了。我們可以使用命令`cat jstack.log | grep "java.lang.Thread.State" | sort -nr | uniq -c`來對jstack的狀態有一個整體的把握,如果WAITING之類的特別多,那么多半是有問題啦
方法二:通過arthas定位
[Arthas]支持直接通過`thread`子命令顯示占用cpu最高的n個線程
展示當前最忙的前3個線程并打印堆棧:
```
$ thread -n 3
"as-command-execute-daemon" Id=29 cpuUsage=75% RUNNABLE
at sun.management.ThreadImpl.dumpThreads0(Native Method)
at sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:440)
at com.taobao.arthas.core.command.monitor200.ThreadCommand$1.action(ThreadCommand.java:58)
at com.taobao.arthas.core.command.handler.AbstractCommandHandler.execute(AbstractCommandHandler.java:238)
at com.taobao.arthas.core.command.handler.DefaultCommandHandler.handleCommand(DefaultCommandHandler.java:67)
at com.taobao.arthas.core.server.ArthasServer$4.run(ArthasServer.java:276)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Number of locked synchronizers = 1
- java.util.concurrent.ThreadPoolExecutor$Worker@6cd0b6f8
"as-session-expire-daemon" Id=25 cpuUsage=24% TIMED_WAITING
at java.lang.Thread.sleep(Native Method)
at com.taobao.arthas.core.server.DefaultSessionManager$2.run(DefaultSessionManager.java:85)
"Reference Handler" Id=2 cpuUsage=0% WAITING on java.lang.ref.Reference$Lock@69ba0f27
at java.lang.Object.wait(Native Method)
- waiting on java.lang.ref.Reference$Lock@69ba0f27
at java.lang.Object.wait(Object.java:503)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
```
根據Arthas`thread`命令打印出來的堆棧信息定位具體的業務代碼,review代碼并嘗試定位邏輯。如有必要,
還可以通過`watch`子命令監聽某個方法的調用次數和資源占用情況
> 要注意的是,arthas的cpu占比,和前面兩種cpu占比統計方式不同。前面兩種針對的是Java進程啟動開始到現在的cpu占比情況,arthas這種是一段采樣間隔內,當前JVM里各個線程所占用的cpu時間占總cpu時間的百分比
### 定位具體方法
1) GC線程
如果是GC線程一直在占用cpu,那么就基本確定是內存泄漏。進一步按照內存問題定位
2) 業務線程
如果是業務線程,那么根據下一節的方法,繼續定位是哪些代碼占用cpu
* io wait
* 比如磁盤空間不夠導致的io阻塞
* 等待內核態鎖,如 synchronized
* `jstack -l pid | grep BLOCKED`查看阻塞態線程堆棧
* dump 線程棧,分析線程持鎖情況
* arthas提供了`thread -b`,可以找出當前阻塞其他線程的線程。針對 synchronized 情況
- 前言
- Write once, run anywhere
- 概述
- JAVA虛擬機
- JVM整體結構
- JVM架構模型
- JVM虛擬機分類
- HotSpot VM
- JRockit
- IBM-J9
- Azul/zing VM
- Taobao VM
- Dalvik VM
- Graal VM
- JAVA源碼編譯機制
- Javac編譯器
- 分析和輸入到符號表
- 注解處理
- 語義分析和生成class文件
- ECJ編譯器
- 類執行機制
- 字節碼解釋執行
- 棧頂緩存
- 部分棧幀共享
- 編譯執行
- 即時編譯器
- C1 Compiler
- C2 Compiler
- Graal編譯器
- C1與C2編譯器
- AOT
- 編譯優化
- 字符串優化
- 方法內聯
- 逃逸分析
- 同步消除
- 標量替換
- 棧上分配
- 去虛擬化/逆優化
- 多層編譯
- JVM編譯策略
- OSR編譯
- 冗余削除
- CodeCache
- 常量編譯優化
- JVM運行時數據區
- 程序計數器
- JAVA虛擬機棧
- 棧幀
- 局部變量表
- 操作數棧
- 本地方法棧
- Java調用native方法
- JVM Stacks && Native Stacks
- 堆-Heap
- 方法區(Method Area)
- 運行時常量池
- 常量傳播優化
- MetaSpace
- 直接內存
- StackOverflowError
- 遞歸方法
- OutOfMemoryError
- 本地內存溢出
- 執行引擎
- 運行時數據區關聯關系
- jdk8內存結構
- JMM內存模型
- JAVA內存模型
- JMM八種操作指令
- 內存屏障
- 指令重排
- as-if-serial語義
- Happen-Before規則
- 數據依賴性
- 原子性、可見性與有序性
- 偽共享
- CPU三級緩存
- 緩存行
- MESI協議
- Java中的偽共享
- ConcurrentHashMap偽共享解決方案
- 虛擬機對象
- 對象創建原理
- 對象內存布局
- 對象頭
- 實例數據
- 對象的訪問定位
- 垃圾收集器與內存分配策略
- GC相關概念
- TLAB
- JVM GC工作原理
- 內存管理
- JAVA引用分類
- 死亡標記
- 回收方法區
- 三色標記算法
- 垃圾收集算法
- 標記-清除算法
- 標記-整理算法
- 復制算法
- 分代收集算法
- HotSpot算法實現
- STW
- 垃圾收集器
- 常見的垃圾收集器
- 垃圾收集器分類
- Serial收集器
- Serial Old收集器
- ParNew收集器
- Parallel Scavenge收集器
- Parallel Old收集器
- CMS收集器
- CMS完整收集過程
- Card Table
- G1收集器
- 分代收集
- 空間整合
- 可預測的停頓時間模型
- G1&CMS
- 主要參數說明
- G1適用場景
- Remembered Set
- G1垃圾回收的過程
- G1優化建議
- Shenandoah
- ZGC
- 垃圾收集器特點
- GC日志
- GC策略的評價指標
- jvm card table數據結構
- 對象生存軌跡
- 類文件結構
- 魔數
- 版本號
- 常量池
- 訪問標志
- 父類索引
- 接口集合
- 字段集合
- 方法集合
- 屬性集合
- 類加載機制與類的初始化
- Java代碼執行流程
- 類加載過程
- 抽象類ClassLoader
- 常見類加載器
- BootstrapClassLoader
- 自定義類加載器
- 線程上下文類加載器
- 雙親委派模型
- Tomcat類加載機制
- ServiceLoader
- 類的初始化
- 常見的JVM類加載異常
- ClassNotFoundException
- NoClassDefFoundError
- LinkageError
- ClassCastException
- 虛擬機性能調優監控與故障處理工具
- CPU利用率高/飆升
- 排查及解決方案
- 上下文切換
- GC問題定位解決方案
- prommotion failed
- FullGC頻繁
- youngGC
- 內存問題
- 內存溢出和內存泄漏
- 內存溢出
- 棧溢出
- 堆溢出
- 對外內存溢出
- 內存泄漏
- 磁盤問題
- 線上問題解決方案
- 不定期出現的接口耗時現象
- 線程池異常
- 死鎖問題
- JVM調優
- jvm參考配置
- jvm-jstat
- jvm-jmap
- jvm-jstack
- jinfo
- jps
- 虛擬機的退出
- Shutdown Hook
- JVM指令
- 附錄
- 常用JVM指令
- Class文件版本號
- Class文件格式
- 方法訪問標識
- jvm常量池
- 類或接口的訪問標識
- 描述符標識字符含義
- 字段訪問標識
- Java程序與Docker容器環境
- 基準測試