Java應用性能分析工具:async-profiler(配合FlameGraph生成火焰圖)

原文鏈接:https://www.jianshu.com/p/9364028cca4e

前言:

及時對項目或者服務器Java應用性能進行性能檢測,並且分析檢測結果數據,發現熱點代碼是一項充滿意義的工作,因爲可能因爲某一段熱點代碼會拖慢整個系統的運行,這是不可忍受的,發現熱點代碼之後需要及時進行代碼優化,並且重複檢測,多多角度檢測,來360無死角的發現項目的性能瓶頸,讓運行着的項目是最優化的。這也是每一位開發者的義務。

發現熱點代碼的前提是可以獲取java應用運行時的profile數據,而採集這些數據需要較爲底層的技術,還好目前有大量的開源根據可以進行這項非常有挑戰性的工作,但是似乎每種工具採集的數據都有所差異(待考證),本文將介紹一種強大而輕量級的java應用運行時profile數據採集工具,在此之前,可以參考使用火焰圖進行Java應用性能分析來大概瞭解java應用性能分析的一些基本情況,該文章中介紹的工具lightweight-java-profiler和本文要介紹的async-profiler都是類似的,但是前者我在自己的電腦上(macOS Sierra 10.12.4)上沒有正常啓動起來,錯誤大概是說段錯誤,但是可以在linux上正常啓動並且可以採集到數據,結合火焰圖生成工具FlameGraph可以進行java應用性能分析,喔對,你應該去學習一下如何從火焰圖中找到熱點代碼,也就是你要學會看火焰圖,這是一種非常重要的技能。也是進行應用性能分析的基礎。下面開始詳細介紹如何使用async-profiler來進行java應用性能分析。

環境準備

首先,你需要從github將代碼下載下來:


git clone https://github.com/jvm-profiling-tools/async-profiler

然後,進入到下載好的項目中,然後進行編譯:


cd async-profiler
make

等待編譯完成,可以在看到項目中多了一個build文件夾,這就是我們需要的東西,值得注意的是,async-profiler是少有的我在編譯的時候沒有遇到任何問題的工具,這也說明這個工具的易用性。當然,下面這些內容是必須的:

  • JAVA_HOME
  • GCC

關於async-profiler到底能做些什麼事情,可以參考下面的描述:


async-profiler can trace the following kinds of events:

  • CPU cycles
  • Hardware and Software performance counters like cache misses, branch misses, page faults, context switches etc.
  • Allocations in Java Heap
  • Contented lock attempts of Java monitors

我主要關心的是CPU profiling這一個功能點,所以本文的重點也在CPU profiling這一個功能點上,其他的功能點可以自行去探索。關於async-profiler實現CPU profiling的原理以及爲什麼這麼做,直接參考github上的readme就可以了,就不再這裏贅述了,下面來看一下到底如何使用這個工具進行java應用的性能分析。

可以發現在async-profiler項目中有一個腳本叫做“profile.sh”,運行這個腳本,會輸出如下提示內容:


Usage: ./profiler.sh [action] [options] <pid>
Actions:
  start             start profiling and return immediately
  stop              stop profiling
  status            print profiling status
  list              list profiling events supported by the target JVM
  collect           collect profile for the specified period of time
                    and then stop (default action)
Options:
  -e event          profiling event: cpu|alloc|lock|cache-misses etc.
  -d duration       run profiling for <duration> seconds
  -f filename       dump output to <filename>
  -i interval       sampling interval in nanoseconds
  -b bufsize        frame buffer size
  -t                profile different threads separately
  -o fmt[,fmt...]   output format: summary|traces|flat|collapsed

<pid> is a numeric process ID of the target JVM
      or 'jps' keyword to find running JVM automatically using jps tool

Example: ./profiler.sh -d 30 -f profile.fg -o collapsed 3456
         ./profiler.sh start -i 999000 jps
         ./profiler.sh stop -o summary,flat jps

其中幾個重要的命令解釋如下:

  • start : 開始進行應用的profile數據採集,如果沒有設定採集時間的話會一直運行下去直到遇到stop命令
  • stop: 和start配合使用,用來停止應用的profile數據採集
  • status:檢測工具的運行狀態,比如可以看到是否已經不可用,或者已經運行多少時間了等信息
  • list:將可以採集的profile數據類型打印出來
  • -d N: 設定採集應用profile數據的時間,單位爲秒
  • -e event:指定採集數據類型,比如cpu

其他的命令可以參考說明,並且可以結合自己實際操作來查看效果。下面來開始使用async-profiler工具來採集cpu profile數據,並且配合火焰圖生成工具工具FlameGraph來生成cpu火焰圖,並且從火焰圖中找到熱點代碼。FlameGraph工具可以直接下載下來就可以使用:


git clone https://github.com/brendangregg/FlameGraph

首先將java應用運行起來,你可以試着運行下面的代碼來進行測試:


import java.io.File;

class Target {
    private static volatile int value;

    private static void method1() {
        for (int i = 0; i < 1000000; ++i)
            ++value;
    }

    private static void method2() {
        for (int i = 0; i < 1000000; ++i)
            ++value;
    }

    private static void method3() throws Exception {
        for (int i = 0; i < 1000; ++i) {
            for (String s : new File("/tmp").list()) {
                value += s.hashCode();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        while (true) {
            method1();
            method2();
            method3();
        }
    }
}

運行起來之後,可以使用jps命令來查看運行起來的java應用的pid,然後使用下面的命令開始使用工具進行cpu profile數據採集:


./profiler.sh start $pid

一段時間之後,比如30秒後,就可以使用下面的命令來停止數據採集了:


./profiler.sh stop $pid

然後,會打印處下面的信息:

可以很直觀的看出,佔用cpu時間最多的是method3,佔用了93.06%的cpu時間,然後是method2和method1,分別佔用2.93%和2.77%的cpu時間,所以很明顯method3就是性能瓶頸,也就是所謂的熱點代碼,需要着手進行優化。當然,上面是有的命令式是比較簡單的,下面來介紹一個比較厲害的命令,可以設定採集數據的時間,並且可以將採集到的數據dump起來,然後使用FlameGraph工具來生成火焰圖進行直觀的分析。當然,首先需要運行起來代碼,並且使用jps找到應用的pid,然後可以使用下面的命令來進行數據採集任務:


./profiler.sh -d 10 -o collapsed -f /tmp/collapsed.txt pid

這個命令的意思是說,採集數據的時間爲10秒,並且將數據按照collapsed規範進行dump,並且dump到/tmp/collapsed.txt這個文件,過了10秒之後,工具會自動停止,並且將cpu的profile數據dump到指定的路徑(按照指定的規範),可以到/tmp/collapsed.txt查看具體的文件內容,但是很大程度上是看不懂的,所以需要使用FlameGraph工具來進行加工一下,可以使用下面的命令來生成火焰圖:


~/github/FlameGraph/flamegraph.pl --colors=java /tmp/collapsed.txt > flamegraph.svg

當然,你需要指定你自己的FlameGraph的路徑,上面命令中的是我的路徑,很快,你就可以在當前目錄下發現多了一個flamegraph.svg文件,使用chorm打開,就可以看到下面的圖片內容(可以點擊放大的):

可以看到,method3是最寬的,也就代表method3佔用的cpu時間是最多的,這樣看起來就直觀很多了。

下面來看一下alloc類型的數據式怎麼生成的,可以從這些數據中看出什麼,運行下面的代碼:


import java.util.concurrent.ThreadLocalRandom;

public class AllocatingTarget implements Runnable {
    public static volatile Object sink;

    public static void main(String[] args) {
        new Thread(new AllocatingTarget(), "AllocThread-1").start();
        new Thread(new AllocatingTarget(), "AllocThread-2").start();
    }

    @Override
    public void run() {
        while (true) {
            allocate();
        }
    }

    private static void allocate() {
        if (ThreadLocalRandom.current().nextBoolean()) {
            sink = new int[128 * 1000];
        } else {
            sink = new Integer[128 * 1000];
        }
    }
}

然後使用jps命令取到該應用的pid,然後執行下面的命令:


./profiler.sh start  -e alloc pid

一段時間之後,可以使用下面的命令來停止數據採集:


./profiler.sh stop  -e alloc pid

然後就會看到下面的輸出:

可以看出各種類型的對象生成量,並且可以看到是從什麼路徑生成的(所謂路徑就是類->方法->方法->...),當然,這只是該工具的一種玩法,其他複雜而有趣的玩法需要不斷挖掘,並且結合實際應用來發現。

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