其實很早以前就據說過Arthas這個工具,只知道是在線診斷工具,也一直沒有去了解,該怎麼用?有啥用?據說好像很厲害?也一直停留在據說階段,不知道你們有沒有同感。可是在去年(2019)下半年的時候須要處理的生產環境問題愈來愈多,也愈來愈複雜,定位問題變得愈來愈繁瑣,總結起來遇到最多的問題就是如下幾點:html
當我看到官網簡介的時候,嗯?!這不就是專門爲我準備的嗎?我決定玩兒一下,盤它!java
Arthas 是Alibaba開源的Java診斷工具,深受開發者喜好。git
當你遇到如下相似問題而一籌莫展時,Arthas能夠幫助你解決:github
Arthas支持JDK 6+,支持Linux/Mac/Winodws,採用命令行交互模式,同時提供豐富的 Tab 自動補全功能,進一步方便進行問題的定位和診斷。web
Arthas的使用方式很是簡單spring
直接到github releases中下載本身想要的版本,arthas-bin.zip docker
解壓後能夠看到有不少jar包,咱們須要使用的就是arthas-boot.jar,由於我以前用過其餘版本因此我把它修改一下名字arthas-boot-3.3.6.jar 數組
你們能夠看到壓縮包中是有arthas-demo.jar的,可是demo裏面的場景比較少,因此我這邊本身模擬咱們常常遇到的場景,也方便咱們修改和調試。我這邊就拿我前兩天作Spring Boot 使用docker整合MongoDB的demo來用。模擬了最多見的一些場景,還原上面的幾個點。我這邊直接添加了一個Controller模擬了常規方法和死鎖,而後用Arthas來排查問題,首先須要啓動項目。瀏覽器
package com.example.mongo.controller;
import com.example.mongo.entity.UserEntity;
import com.example.mongo.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.Resource;
import lombok.SneakyThrows;
@RestController
@RequestMapping("arthas")
public class ArthasController {
private static ExecutorService executors = Executors.newFixedThreadPool(2);
private static String NAME = "DW";
@Resource
private UserService userService;
@SneakyThrows
@GetMapping("normal")
public List<String> normal( @RequestParam("param") Integer param ) {
if (Objects.isNull(param)) throw new IllegalArgumentException("param is null");
userService.findById(1L);
return Arrays.asList("david", "david1");
}
@GetMapping("deadlock")
public void deadlock() {
UserEntity a = new UserEntity();
UserEntity b = new UserEntity();
executors.execute(() -> {
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "get A lock! start sleep");
NAME = Thread.currentThread().getName() + "1a";
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "sleep end start get B lock!");
synchronized (b) {
NAME = Thread.currentThread().getName() + "1b";
System.out.println(Thread.currentThread().getName() + "get B lock!");
}
}
});
executors.execute(() -> {
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "get B lock! start sleep");
NAME = Thread.currentThread().getName() + "2b";
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "sleep end start get A lock!");
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "get A lock!");
NAME = Thread.currentThread().getName() + "2a";
}
}
});
}
}
// userService#findById
@Override
@SneakyThrows
public UserEntity findById( Long id ) {
List<UserEntity> david = this.findByName("david");
if (!CollectionUtils.isEmpty(david)) {
david.forEach(System.out::println);
}
Thread.sleep(100);
return userRepository.findById(id).orElse(new UserEntity());
}
複製代碼
[INFO] arthas-client connect 127.0.0.1 3658
由於咱們是本地調試因此能夠直接使用瀏覽器訪問localhost:3658
,在web console中來操做,效果以下。【Arthas官網】 默認狀況下,arthas只listen 127.0.0.1,因此若是想從遠程鏈接,則可使用 --target-ip參數指定listen的IP,更多參考-h的幫助說明。 注意會有安全風險,考慮下面的tunnel server的方案。安全
這種場景下咱們可使用watch命令監控咱們的目標方法,查看入參,出參和異常信息。
命令格式:watch <類的全限定名> <方法名> '{params, returnObj, throwExp}'
params若是是數組的話可使用params[0],params[1]
來查看,returnObj
表示返回值,throwExp
表示異常信息,若是隻想看入參,只寫params信息就能夠,returnObj
和throwExp
也是同樣。輸入watch com.example.mongo.controller.ArthasController normal '{params, returnObj, throwExp}'
回車即開始監控。
不傳參試一下http://localhost:8080/arthas/normal?param
,報錯了,param is null
。
傳參http://localhost:8080/arthas/normal?param=1
,入參出參都打印了,可是沒有具體值,修改一下命令,watch com.example.mongo.controller.ArthasController normal '{params[0], returnObj[0], returnObj[1], throwExp}'
從新執行
修改命令以後效果以下:
兩種辦法:
既然主要是講Arthas那確定是選擇Arthas啦,下次必定! 這裏須要使用到jad
命令,直接對咱們的目標類或者目標方法進行反編譯查看。
命令格式:jad <類的全限定名>/<類的全限定名> <方法名>
jad com.example.mongo.controller.ArthasController normal
回車,便可查看正在運行的代碼究竟是什麼樣子,是美髮不上去呢,仍是寫bug,拒絕甩鍋。因爲類信息較長,就不截圖了輸入jad com.example.mongo.controller.ArthasController
回車便可查看。命令格式:trace <類的全限定名> <方法名>
trace com.example.mongo.controller.ArthasController normal
回車,能夠看到時間主要消耗在UserService#findById()
繼續追蹤trace com.example.mongo.service.UserService findById
,能夠看到總耗時爲109ms,方法耗時綜合在9ms左右,Thread.sleep(100)不會打印,這個時候對比一下代碼就能夠鎖定具體問題了。
命令格式:stack <類的全限定名> <方法名>
stack com.example.mongo.controller.ArthasController normal
命令格式:getstatic <類的全限定名> <方法名>
getstatic com.example.mongo.controller.ArthasController NAME
回車。
http://localhost:8080/arthas/deadlock
復現死鎖,如今兩個線程相互等待對方釋放資源。
Thread
命令來查看咱們的線程信息。能夠看到pool-1-thread-1
和pool-1-thread-2
阻塞了,這也是爲何建議你們在使用線程池的時候必定要命名的緣由,前面線程池的blog有詳細講解。傳送門Davids原理探究:ThreadPoolExecutor原理。
thread -b
直接查看block狀態的線程信息,鎖定緣由。
thread -n <num>
查看cpu使用率最高的num個線程用於排查cpu佔用高的場景,相關信息也很是詳細。
通常在配置JVM的時候建議打開dump日誌,一旦出現內存溢出方便排查,可是在內存達到報警閾值或者內存在某個版本發佈以後長期處於高佔用狀態的時候,咱們能夠採用Arthas進行heapdump分析緣由(相似jmap命令的heap dump功能)。
命令格式:
heapdump <path> 示例:heapdump /tmp/dump.hprof
heapdump --live <path> 示例:heapdump --live /tmp/dump.hprof
heapdump 示例:heapdump
查看dump文件生成成功,因爲篇幅緣由,如何排查dump後面再專門作一篇blog。
其實Arthas還有很是多的豐富的功能,本篇博客只是介紹了一些很是基礎和我經常使用的,並且這些命令還有不少高級用法,你們感興趣的能夠去玩一玩。官網傳送門