【死磕JVM】用Arthas排查JVM記憶體 真爽!我從小用到大

牧小農發表於2021-05-13

在這裡插入圖片描述

Arthas是啥

當我們系統遇到JVM或者記憶體溢位等問題的時候,如何對我們的程式進行有效的監控和排查,就發現了幾個比較常用的工具,比如JDK自帶的 jconsole、jvisualvm還有一個最好用的工具——jprofiler,但是這個是收費的,或者除了很有錢的公司,一般很少人會用這個,還有一個就是我們今天的主角——Arthas ,為什麼今天會重點講這個呢?

官網地址:http://arthas.gitee.io/
GitHub地址:https://github.com/alibaba/arthas/

Arthas 是Alibaba開源的Java診斷工具,採用命令列互動模式,提供了較為豐富的功能,主要還是他是免費裡面的算是好用且功能比較強大的一個JVM排查的外掛,在瞭解這個利器之後,發現還是挺好用的,而且支援的功能也比較全面,那麼Arthas到底可以為我們做哪些事情呢?

  1. 提供效能看板,包括執行緒、cpu、記憶體等資訊,並且會定時的重新整理。
  2. 根據各種條件檢視執行緒快照。找出cpu佔用率最高的n個執行緒
  3. 輸出jvm的各種資訊,如gc演算法、jdk版本、ClassPath等
  4. 遇到問題無法線上上 debug,熱部署加日誌直接替換
  5. 檢視某個類的靜態屬性,也可以通過ognl語法執行一些語句
  6. 檢視已載入的類的詳細資訊,這個類從哪個jar包載入的,檢視類的方法的資訊
  7. dump 類的位元組碼到指定目錄
  8. 直接反編譯指定的類
  9. 快速定位應用的熱點,生成火焰圖
  10. 可以監控到JVM的實時執行狀態

以前,你碰到這些問題,解決的辦法大多是,修改程式碼,重新上線。但是在大公司裡,上線的流程是非常繁瑣的,如果為了多加一行日誌而重新發布版本,無疑是非常折騰人的。但是阿里巴巴開源的Arthas
有了更為優雅的線上除錯方法。

Arthas 支援JDK6,同時可以在 Linux/Mac/Windows上執行,自動Tab 補全功能,更方便我們定位問題和診斷

下載地址:https://arthas.gitee.io/download.html
你可以下載zip的包我下載的是arthas-packaging-3.5.0-bin.zip
或者通過命令去下載

wget https://alibaba.github.io/arthas/arthas-boot.jar

使用手冊

1. 快速啟動

當我們下載好之後,我們直接通過命令啟動就可以java -jar arthas-boot.jar,但是在此之前我們需要通過檢測的程式碼來掛靠到Arthas上面

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 FullGCTest {


    //模擬銀行卡的類
    private static class CardInfo {
        //小農的銀行卡資訊記錄
        BigDecimal price = new BigDecimal(10000000.0);
        String name = "牧小農";
        int age = 18;
        Date birthdate = new Date();

        public void m() {}
    }

    //執行緒池 定時執行緒池
    //50個,然後設定 拒絕策略
    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(() -> {
                //呼叫M方法
                info.m();

            }, 2, 3, TimeUnit.SECONDS);
        });
    }

    private static List<CardInfo> getAllCardInfo(){
        List<CardInfo> taskList = new ArrayList<>();
        //每次查詢100張卡出來
        for (int i = 0; i < 100; i++) {
            CardInfo ci = new CardInfo();
            taskList.add(ci);
        }

        return taskList;
    }
}

這個是上篇文章講述的案例,感興趣的可以瞭解一下。

首先我們需要使用javac 命令將Java檔案進行編譯javac FullGCTest.java進行編譯,然後列印GC日誌,進行風險監控列印GC日誌:
java -Xms200M -Xmx200M -XX:+PrintGC FullGCTest

Arthas啟動命令:java -jar arthas-boot.jar

在這裡插入圖片描述

我們就看到了我們剛才啟動的FullGCTest的應用程式,我們輸入編號 1 回車,這樣我們就把Arthas掛靠到我們的程式上,接下來我們只需要做對應的命令操作就可以了
在這裡插入圖片描述

命令詳情文件:https://arthas.aliyun.com/doc/commands.html

2. 功能列表

命令 詳細說明
jvm 檢視當前JVM資訊
thread 檢視當前JVM的執行緒堆疊資訊
watch 方法執行資料觀測
dashboard 當前系統的實時資料皮膚
trace 方法內部呼叫路徑,並輸出方法路徑上的每個節點上耗時
stack 輸出當前方法被呼叫的呼叫路徑
tt 方法執行資料的時空隧道,記錄下指定方法每次呼叫的入參和返回資訊,並能對這些不同的時間下呼叫進行觀測
vmoption 檢視,更新JVM已載入的類資訊
sc 檢視JVM已載入的類資訊
sm 檢視已載入類的方法資訊
jad 反編譯指定已載入類的原始碼
classloader 檢視classloader的繼承樹,urls,類載入資訊
heapdump 類似jmap命令的heap dump 功能

jvm

在這裡插入圖片描述

OPERATING-SYSTEM:系統相關引數

THREAD相關:

  • COUNT : JVM當前活躍的執行緒數
  • DAEMON-COUNT : JVM當前活躍的守護執行緒數
  • PEAK-COUNT: 從JVM啟動開始曾經活著的最大執行緒數
  • STARTED-COUNT: 從JVM啟動開始總共啟動過的執行緒次數
  • DEADLOCK-COUNT: JVM當前死鎖的執行緒數

MEMORY

FILE-DESCRIPTOR(檔案描述符相關):

  • MAX-FILE-DESCRIPTOR-COUNT:JVM程式最大可以開啟的檔案描述符數
  • OPEN-FILE-DESCRIPTOR-COUNT:JVM當前開啟的檔案描述符數

thread 命令

引數說明:

命令 詳細說明
id 執行緒id
[n:] 指定最忙的前N個執行緒並列印堆疊
[b] 找出當前阻塞其他執行緒的執行緒
[i] 指定cpu使用率統計的取樣間隔,單位為毫秒,預設值為200
[--all] 顯示所有匹配的執行緒

列印當前最忙的N個執行緒並列印堆疊
thread -n 3

在這裡插入圖片描述

thread 檢視所有執行緒
在這裡插入圖片描述

thread 17: 顯示指定執行緒的執行堆疊
在這裡插入圖片描述

thread -i: 指定取樣時間間隔

thread -i 1000 : 統計最近1000ms內的執行緒CPU時間。
thread -n 3 -i 1000 : 列出1000ms內最忙的3個執行緒棧

dashboard 命令

執行程式時,會顯示當前程式的實時資訊,如qps, rt, 錯誤數, 執行緒池資訊等等

在這裡插入圖片描述

資料說明:

  • ID: Java級別的執行緒ID
  • NAME: 執行緒名
  • GROUP: 執行緒組名
  • PRIORITY: 執行緒優先順序, 1~10之間的數字,越大表示優先順序越高
  • STATE: 執行緒的狀態CPU%: 執行緒的cpu使用率。比如取樣間隔1000ms,某個執行緒的增量cpu時間為100ms,則cpu使用率=100/1000=10%
  • DELTA_TIME: 上次取樣之後執行緒執行增量CPU時間,資料格式為秒
  • TIME: 執行緒執行總CPU時間,資料格式為分:秒
  • INTERRUPTED: 執行緒當前的中斷位狀態
  • DAEMON: 是否是daemon執行緒

在這裡插入圖片描述

引數說明:

引數名稱 詳細說明
id 重新整理實時資料的時間間隔 (ms),預設5000ms
[n:] 重新整理實時資料的次數

sc 命令

檢視JVM已載入的類資訊,通過SC我們可以看到我們這個類的詳細資訊,包括是從哪個jar包讀取的,他是不是介面/列舉類等,甚至包括他是從哪個類載入器載入的。

引數說明:

引數名稱 詳細說明
class-pattern 類名錶達式匹配
method-pattern 方法名錶達式匹配
[d] 輸出當前類的詳細資訊,包括這個類所載入的原始檔案來源、類的宣告、載入的ClassLoader等詳細資訊。如果一個類被多個ClassLoader所載入,則會出現多次
[E] 開啟正規表示式匹配,預設為萬用字元匹配

sc -d *CardInfo: 列印類的詳細資訊
在這裡插入圖片描述

sc -d -f *CardInfo:列印類的Fiedld資訊

在這裡插入圖片描述

heapdump + jhat分析

heapdump:類似於jmap命令

建立到指定資料夾下:

[arthas@365564]$ heapdump /usr/local/mxn/dump.hprof
Dumping heap to /usr/local/mxn/dump.hprof ...
Heap dump file created

建立成功後,我們就可以在指定資料夾下看到對應的dump檔案,然後使用命令jhat dump.hprof,生成檔案,成功後我們就可以通過IP+埠進行訪問了
在這裡插入圖片描述

訪問:
在這裡插入圖片描述

然後我們就可以通過IP+埠去訪問它了,裡面有個他的other,我們拉到最底下,找
Show instance counts for all classes (including platform)
在這裡插入圖片描述

從下面我們可以分析出來哪個類包含的物件最多,分析出來哪個類產生的物件

在這裡插入圖片描述

這個裡面最強大的功能還是叫做 Execute Object Query Language (OQL) query,這個裡面可以顯示有哪些物件,物件有多少個位元組和引用,可以觀察到哪個物件產生了問題,如下圖所示,顯示所有String對應的物件
在這裡插入圖片描述
在這裡插入圖片描述

搜尋點進去之後我們還能看到這個物件到底佔用了多少個位元組,有多少個引用指向了這個Object,這個OQL的語法也是很靈活,我們可以使用where條件去過濾

在這裡插入圖片描述

jad

jad:反編譯某個類,或者反編譯某個類的某個方法,動態代理生成類的問題定位 第三方的類(觀察程式碼) 版本問題(確定自己最新提交的版本是不是被使用)

在這裡插入圖片描述

有人可能會問這個有啥用,原始碼我不是自己就知道嗎?因為有時我們經常會不確定線上或者測試環境的包是否是我們修改過的,這時候就可以通過jad反編譯來看下,是否是最新的程式碼

redafine

redafine:熱替換,動態更新程式碼,不用重啟jvm目前有些限制條件:只能改方法實現(方法已經執行完成),不能改方法名, 不能改屬性 m() -> mm()

比如我們線上上環境有個class確認有問題,想要重新替換,一般情況下只能停掉伺服器重新發布,在普通的小公司這樣是可以的,但是在大規模公司京東淘寶這樣的是不能停的,因為整個流程是非常複雜的,那怎麼辦呢?大家可以看到下面的案例

首先我們新建一個測試案例:

public class T{
    public static void main(String[] args) throws Exception{
                    for(;;){
                    System.in.read();
                    new TT().m();
                }
        }
}
public class TT{
        public void m(){
        System.out.println(2);
    }
}

使用命令javac *.java,編譯成class檔案,然後執行 T 檔案

[root@VM-0-7-centos t]# java T
a
2
2

當我們輸入a的時候列印2,但是我們上線以後才發現,我們需要輸出的1,這個是如果要從本地更改要重新發布上線,為了這一個修改,明顯是不值當的,但是如果我們用 redafine 熱部署就可以幫助我們直接替換,不用重新發布jvm

然後我們將 T 這個程式掛靠到 Arthas 上面去
在這裡插入圖片描述

然後我們直接修改 TT.java 程式 vi TT.java,將裡面列印2的值修改成1

public class TT{
        public void m(){
            System.out.println(1);
        }
}

然後編譯執行 ```javac TT.java ````

在回到我們掛靠的Arthas 上面執行 redefine /usr/local/mxn/fuccGc/t/TT.class

[arthas@398842]$ redefine /usr/local/mxn/fuccGc/t/TT.class
redefine success, size: 1, classes:
TT

執行成功
大家可以看到我們在沒有重新啟動的情況下成功替換了class檔案
在這裡插入圖片描述

watch

watch:方法執行的資料觀測,可以通過watch指令,來監控某個類,監控後,執行下你的功能,復現下場景,arthas會提供給你具體的出參和入參,幫助你排查故障

trace

輸出方法呼叫路徑,並輸出耗時,這個指令對於優化程式碼非常的有用,可以看出具體每個方法執行的時間,如果是for迴圈等重複語句,還能看出n次迴圈中的最大耗時,最小耗時,和平均耗時,完美!

tt

在我們對某個方法開啟tt後,會記錄每一次呼叫(我們可以設定最大監控次數)的入參和返回引數,並能對這些不同時間下調進行觀測

[arthas@405136]$ tt -t FullGCTest modelFit

在這裡插入圖片描述

命令引數解析-t
tt 命令有很多個主引數,-t 就是其中之一。這個引數的表明希望記錄下類 *Test 的 print 方法的每次執行情況。
-n 3
當你執行一個呼叫量不高的方法時可能你還能有足夠的時間用 CTRL+C 中斷 tt 命令記錄的過程,但如果遇到呼叫量非常大的方法,瞬間就能將你的 JVM 記憶體撐爆。

此時你可以通過 -n 引數指定你需要記錄的次數,當達到記錄次數時 Arthas 會主動中斷tt命令的記錄過程,避免人工操作無法停止的情況。

ognl表示式

ognl表示式

OGNL特殊用法請參考:https://github.com/alibaba/arthas/issues/71
OGNL表示式官方指南:https://commons.apache.org/proper/commons-ognl/language-guide.html

呼叫靜態函式:ognl '@java.lang.System@out.println("hello")'
獲取靜態類的靜態欄位:ognl '@FullGCTest@random'

Arthas還支援Web Console,詳見:https://alibaba.github.io/arthas/web-console.html

總結

Arthas是一個線上Debug神器,相比於其他工具,Arthas有著比較全面的功能,上手也比較容易,對於剛開始入門的小夥伴也是可以輕鬆掌握的,對於文中有不懂或者有問題的小夥伴,大家可以在下面留言評論。

原創不易,希望大家多多捧場,記得一鍵三連!!!

我是牧小農,怕什麼真理無窮,進一步有進一步的歡喜,大家加油!

相關文章