推薦一個我從小用到大的排查JVM內存神器,真爽!

推薦閱讀:

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 '@[email protected]("hello")'
獲取靜態類的靜態字段:ognl '@FullGCTest@random'

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

總結

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章