MXBean から取得できる情報あれこれ

f:id:Naotsugu:20170216221743p:plain:w300

Java Management Extensions の主要どころの簡単なまとめです。

RuntimeMXBean

ランタイム関連情報の取得。

RuntimeMXBean runtimeMx = ManagementFactory.getRuntimeMXBean();

以下のような情報が取得できます。

メソッド 出力例 説明
getVmName() Java HotSpot(TM) 64-Bit Server VM VM
getVmVersion() 25.102-b14 VMバージョン
getVmVendor() Oracle Corporation VMベンダ名
getSpecName() Java Virtual Machine Specification JVM仕様名
getSpecVersion() 1.8 JVM仕様のバージョン
getSpecVendor() Oracle Corporation JVM仕様のベンダ名
getManagementSpecVersion() 1.2 管理インタフェースの仕様のバージョン
getName() 95264@hostname 実行しているJVMの名前。sunの実装では@より前がPID
new Date(getStartTime()).toString() Mon Feb 01 00:00:00 JST 2017 起動日時
getUptime() 起動してからの経過時間(ms)
getInputArguments().toString() [-Dfile.encoding=UTF-8, ...] 入力引数
getClassPath() /.../jre/lib/charsets.jar:/... システムクラスローダーが使用するクラスパスの文字列
getLibraryPath() /.../Java/Extensions:/... ライブラリパス
getBootClassPath() /.../lib/resources.jar:/... ブートストラップクラスローダが使用するクラスパスの文字列
isBootClassPathSupported() ブートストラップクラスパスをサポートするか

CompilationMXBean

JITコンパイラ関連。

CompilationMXBean compilationMx = ManagementFactory.getCompilationMXBean();
if (compilationMx != null) {
    // ...
}

環境によってはnullが帰る場合がありますので念のためnullチェックをしておいた方が良いです。

以下のような情報が取得できます。

メソッド 出力例 説明
getName() HotSpot 64-Bit Tiered Compilers JITコンパイラ
getTotalCompilationTime() JITコンパイルの累積経過時間(ms)
isCompilationTimeMonitoringSupported() コンパイル時間の監視をサポートするか

OperatingSystemMXBean

OS関連情報です。

java.lang.management.OperatingSystemMXBean はあまり情報取れないので、com.sun.management.OperatingSystemMXBean にキャストして使うことが多いです。

OperatingSystemMXBean osMx = (OperatingSystemMXBean) 
    ManagementFactory.getOperatingSystemMXBean();

以下のような情報が取得できます。

メソッド 出力例 説明
getName() Mac OS X OS名
getVersion() 10.12.2 OSバージョン
getArch() x86_64 OSアーキテクチャ
getAvailableProcessors() 8 プロセッサ数
getCommittedVirtualMemorySize() 6.2 GB 利用可能な仮想メモリー容量(byte)
getSystemCpuLoad() 0.1 システム全体のCPU使用率(0.0〜1.0の値)
getSystemLoadAverage() 5.51... 最後の1分のシステム全体ロードアベレージ
getTotalPhysicalMemorySize() 8.6 GB 物理メモリーの合計容量(byte)
getFreePhysicalMemorySize() 58.0 MB 空き物理メモリー容量(byte)
getTotalSwapSpaceSize() 5.4 GB スワップ空間の合計容(byte)
getFreeSwapSpaceSize() 1.5 GB 空きスワップ空間容量(byte)
getProcessCpuLoad() 0.1 VMプロセスのCPU使用率(0.0〜1.0の値)
getProcessCpuTime() 478183000 VMプロセスが使用したCPU時間(ns)

なお、出力例のバイト文字は以下の要領で整形したものです(実際はバイト値がlongで出力されます)。

public static String prettyBytes(long bytes, boolean si) {
    int unit = si ? 1000 : 1024;
    if (bytes < unit) return bytes + " B";
    int exp = (int) (Math.log(bytes) / Math.log(unit));
    String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i");
    return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}

MemoryMXBean

メモリの概要です。

MemoryMXBean memoryMx = ManagementFactory.getMemoryMXBean();

以下のような情報が取得できます。

メソッド 説明
getObjectPendingFinalizationCount() ファイナライズ保留オブジェクト数
getHeapMemoryUsage() ヒープ使用量
getNonHeapMemoryUsage() ノンヒープ使用量
isVerbose() 詳細出力が有効か
setVerbose(boolean value) 詳細出力の有効/無効
gc() GCを実行

MemoryUsage からはもう少し詳細な情報が取得できます。

MemoryUsage memoryUsage = memoryMx.getHeapMemoryUsage();

以下の情報を得ることができます。

メソッド 出力例 説明
getCommitted() 129.0 MB
getInit() 134.2 MB 初期ヒープ容量
getMax() 1.9 GB 利用可能なヒープ容量
getUsed() 4.1 MB 現在利用しているヒープ容量

MemoryPoolMXBean

MemoryMXBean では概要の情報でしたが、MemoryPoolMXBeanでは各種のメモリプールの詳細を見ることができます。

List<MemoryPoolMXBean> memoryPoolMxs = ManagementFactory.getMemoryPoolMXBeans();
for (MemoryPoolMXBean memoryPoolMx : memoryPoolMxs) {
    System.out.println(memoryPoolMx.getName() + ", " +
        Arrays.deepToString(memoryPoolMx.getMemoryManagerNames()));
    MemoryUsage mu = memoryPoolMx.getUsage();
    // ...
}

以下のようなメモリプール達が出力されました。

Code Cache, [CodeCacheManager]
Metaspace, [Metaspace Manager]
Compressed Class Space, [Metaspace Manager]
PS Eden Space, [PS MarkSweep, PS Scavenge]
PS Survivor Space, [PS MarkSweep, PS Scavenge]
PS Old Gen, [PS MarkSweep]

MemoryPoolMXBean から、または MemoryUsage などを経由して各種メモリ関連の情報を取得することができます。

ThreadMXBean

スレッド関連です。

ThreadMXBean thread = (com.sun.management.ThreadMXBean) 
    ManagementFactory.getThreadMXBean();

以下のような情報が取得できます。

メソッド 出力例 説明
getThreadCount() 6 ライブスレッド数
getDaemonThreadCount() 4 デーモンスレッド数
getPeakThreadCount() 6 起動後からのピークスレッド数
getTotalStartedThreadCount() 7 VM起動後に作成されたスレッド数合計
getCurrentThreadCpuTime() 18289000 現在のスレッドの合計 CPU 時間(ns)
getCurrentThreadUserTime() 16570000 現在のスレッドのユーザーモード実行CPU 時間(ns)

個別のスレッドを指定することもできます。

メソッド 説明
getThreadAllocatedBytes(long id) スレッドに割り当てられたヒープメモリー
getThreadCpuTime(long ids) スレッドの合計 CPU 時間(ns)
getThreadUserTime(long ids) スレッドの合計ユーザ CPU 時間(ns)
getThreadInfo(long id) 指定したスレッドIDのThreadInfo
getAllThreadIds() ライブスレッドのスレッドID配列

スレッドIDを指定して ThreadInfo からスタックトレースなど色々な情報を取ることができます。

for (long id : thread.getAllThreadIds()) {
    ThreadInfo info = thread.getThreadInfo(id);
    System.out.println(info); // "Timer-0" Id=10 RUNNABLE など

スレッドがらみはこの他にも色々と情報が取得できます。

ClassLoadingMXBean

クラスローダ関連です。

ClassLoadingMXBean classLoadingMx = ManagementFactory.getClassLoadingMXBean();

以下のような概要が取得できます。

メソッド 出力例 説明
getLoadedClassCount() 669 ロード済みクラス数
getUnloadedClassCount() 1 アンロードされたクラス数累計
getTotalLoadedClassCount() 670 ロードされたクラス数累計
isVerbose() 詳細出力かどうか
setVerbose(boolean value) 詳細出力の切り替え

GarbageCollectorMXBean

GC 関連です。

Collection<GarbageCollectorMXBean> garbageCollectors = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean garbageCollector : garbageCollectors) {
    String gcName = garbageCollector.getName();
    long gcCount = garbageCollector.getCollectionCount();
    long gcTime = garbageCollector.getCollectionTime();
}

以下のような情報を得ることができます。

メソッド 出力例 説明
getName() PS Scavenge 名前
getCollectionCount() 3 GC数の合計
getCollectionTime() 23453 GC時間の累計(ms)

com.sun.management.GarbageCollectorMXBean にキャストすることでより詳細な情報を得ることができます。

com.sun.management.GarbageCollectorMXBean sunGcMx 
    = (com.sun.management.GarbageCollectorMXBean)garbageCollector;
GcInfo gcInfo = gc.getLastGcInfo();

サンプル

System.out に代表的な情報を出力するサンプルです。

import com.sun.management.OperatingSystemMXBean;
import com.sun.management.ThreadMXBean;
import com.sun.management.GarbageCollectorMXBean;
import java.lang.management.*;
import java.util.*;
import java.util.logging.Logger;

public class SystemMonitor {

    private static final Logger logger = Logger.getLogger(SystemMonitor.class.getName());

    private final Timer timer;

    private final RuntimeMXBean runtimeMx;
    private final CompilationMXBean compilationMx;
    private final ThreadMXBean sunThreadMx;
    private final MemoryMXBean memoryMx;
    private final ClassLoadingMXBean classLoadingMx;
    private final OperatingSystemMXBean sunOsMx;
    private final Collection<GarbageCollectorMXBean> garbageCollectors;


    public SystemMonitor() {
        timer = new Timer(true);
        runtimeMx = ManagementFactory.getRuntimeMXBean();
        compilationMx = ManagementFactory.getCompilationMXBean();
        sunThreadMx = (com.sun.management.ThreadMXBean) ManagementFactory.getThreadMXBean();
        memoryMx = ManagementFactory.getMemoryMXBean();
        classLoadingMx = ManagementFactory.getClassLoadingMXBean();
        sunOsMx = (com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
        garbageCollectors = (List<GarbageCollectorMXBean>)(List<?>)ManagementFactory.getGarbageCollectorMXBeans();

    }

    private final String[] systemInfoFmt = {
        ":: System Information",
        "    Operating system     : %s,  Version : %s,  Arch : %s",
        "    Number of processors : %d,  Physical memory : %s,  Virtual memory : %s",
        ":: VM Information",
        "    VM : %s,  Version : %s,  Vendor : %s,  JIT compiler : %s",
        "    Arguments : %s",
        "    Classpath : %s",
        "         boot : %s",
        "    Lib Path  : %s"
    };

    public void systemInfo() {

        print(systemInfoFmt[0]);
        print(String.format(systemInfoFmt[1],
                sunOsMx.getName(),
                sunOsMx.getVersion(),
                sunOsMx.getArch()
        ));
        print(String.format(systemInfoFmt[2],
                sunOsMx.getAvailableProcessors(),
                prettyBytes(sunOsMx.getTotalPhysicalMemorySize()),
                prettyBytes(sunOsMx.getCommittedVirtualMemorySize())
        ));
        print(systemInfoFmt[3]);
        print(String.format(systemInfoFmt[4],
                runtimeMx.getVmName(),
                runtimeMx.getVmVersion(),
                runtimeMx.getVmVendor(),
                (compilationMx != null) ? compilationMx.getName() : "-"
        ));
        print(String.format(systemInfoFmt[5],
                runtimeMx.getInputArguments().toString()
        ));
        print(String.format(systemInfoFmt[6],
                runtimeMx.getClassPath()
        ));
        print(String.format(systemInfoFmt[7],
                runtimeMx.isBootClassPathSupported() ? runtimeMx.getBootClassPath() : "-"
        ));
        print(String.format(systemInfoFmt[8],
                runtimeMx.getLibraryPath()
        ));

    }

    private final String[] tickFmt = {
        ":: System",
        "    CPU usage : %.2f,  Load average(last minute) : %.5f",
        "    Physical memory : %s,  Free : %s",
        "    Swap space : %s,  Free : %s",
        ":: VM Process",
        "    Uptime : %s,  Started : %s",
        "    CPU usage : %.2f,  CPU time : %s,  JIT compile time : %s",
        ":: Heap",
        "    Current : %s,  Committed : %s,  Init : %s,  Max : %s",
        ":: Thread usage",
        "    Live : %d,  Peak : %d,  Daemon : %d,  TotalStarted : %d",
        ":: Class loading",
        "    Current Loaded : %d,  Loaded(total) : %d,  Unloaded(total) : %d",
        ":: Garbage collector",
        "    %s : Count : %d, Elapsed time : %s"
    };

    public void tick() {

        print("");
        print(tickFmt[0]); // System
        print(String.format(tickFmt[1],
                sunOsMx.getSystemCpuLoad(),
                sunOsMx.getSystemLoadAverage()
        ));
        print(String.format(tickFmt[2],
                prettyBytes(sunOsMx.getTotalPhysicalMemorySize()),
                prettyBytes(sunOsMx.getFreePhysicalMemorySize())
        ));
        print(String.format(tickFmt[3],
                prettyBytes(sunOsMx.getTotalSwapSpaceSize()),
                prettyBytes(sunOsMx.getFreeSwapSpaceSize())
        ));
        print(tickFmt[4]); // VM Process 
        print(String.format(tickFmt[5],
                prettyDuration(runtimeMx.getUptime()),
                new Date(runtimeMx.getStartTime()).toString()
        ));
        print(String.format(tickFmt[6],
                sunOsMx.getProcessCpuLoad(),
                prettyDuration(sunOsMx.getProcessCpuTime() / 1000000),
                (compilationMx != null) ? prettyDuration(compilationMx.getTotalCompilationTime()) : "-"
        ));
        print(tickFmt[7]); // Heap
        MemoryUsage mu1 = memoryMx.getHeapMemoryUsage();
        print(String.format(tickFmt[8],
                prettyBytes(mu1.getUsed()),
                prettyBytes(mu1.getCommitted()),
                prettyBytes(mu1.getInit()),
                prettyBytes(mu1.getMax())
        ));
        print(tickFmt[9]); // Thread
        print(String.format(tickFmt[10],
                sunThreadMx.getThreadCount(),
                sunThreadMx.getPeakThreadCount(),
                sunThreadMx.getDaemonThreadCount(),
                sunThreadMx.getTotalStartedThreadCount()
        ));
        print(tickFmt[11]); // Class loading
        print(String.format(tickFmt[12],
                classLoadingMx.getLoadedClassCount(),
                classLoadingMx.getTotalLoadedClassCount(),
                classLoadingMx.getUnloadedClassCount()
        ));

        print(tickFmt[13]); // GarbageCollector
        for (GarbageCollectorMXBean gcMx : garbageCollectors) {
            print(String.format(tickFmt[14],
                    gcMx.getName(),
                    gcMx.getCollectionCount(),
                    prettyDuration(gcMx.getCollectionTime())
            ));
        }
        print("");

    }


    public void start(long period) {

        systemInfo();

        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                tick();
            }
        };
        timer.scheduleAtFixedRate(timerTask, 1 * 1000, period);
    }

    public void stop() {
        timer.cancel();
        timer.purge();
    }

    private void print(String msg) {
        System.out.println(msg);
    }


    public static String prettyBytes(long bytes) {
        return prettyBytes(bytes, true);
    }
    public static String prettyBytes(long bytes, boolean si) {
        int unit = si ? 1000 : 1024;
        if (bytes < unit) return bytes + " B";
        int exp = (int) (Math.log(bytes) / Math.log(unit));
        String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
        return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
    }


    public static String prettyDuration(long millis) {
        long seconds = millis / 1000;
        long d =  seconds / 86400;
        long h = (seconds % 86400) / 3600;
        long m = (seconds % 3600) / 60;
        long s = (seconds % 60);
        long ms = millis % 1000;
        return ((d > 0) ? d + "days" : "") +
                ((h > 0) ? h + "h" : "") +
                ((m > 0) ? m + "m" : "") +
                ((s > 0) ? s + "s" : "") +
                ((ms > 0) ? ms + "ms" : "0");

    }

}

以下のように使えます。

    public static void main(String... args) throws Exception {

        SystemMonitor mx = new SystemMonitor();
        mx.start(2000);

        // ...

        mx.stop();
    }

Java1.6 時代のCPU使用率

OperatingSystemMXBeangetProcessCpuLoad() でCPU使用率が取れるようになりましたが、1.7 からなので、それ以前は以下の要領でCPU使用率を計算できます。

RuntimeMXBean runtimeMx = ManagementFactory.getRuntimeMXBean();
OperatingSystemMXBean osMx = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();

TimerTask task = new TimerTask() {

    final int nCPUs = osMx.getAvailableProcessors();
    long prevUpTime = runtimeMx.getUptime();
    long prevProcessCpuTime = osMx.getProcessCpuTime();

    @Override public void run() {

        final long upTime = runtimeMx.getUptime();
        final long processCpuTime = osMx.getProcessCpuTime();

        final long elapsedCpu  = processCpuTime - prevProcessCpuTime;
        final long elapsedTime = upTime - prevUpTime;
        float cpuUsage = elapsedCpu / (elapsedTime * 10000F * nCPUs);
        cpuUsage = Math.max(0F, Math.min(100F, cpuUsage));

        System.out.println(String.format("%.1f", cpuUsage));

        prevUpTime = upTime;
        prevProcessCpuTime = processCpuTime;
    }
};

new Timer(true).scheduleAtFixedRate(task,5000, 5000);

ちなみに、getProcessCpuLoad()com.sun.management.OperatingSystemMXBean ではなくて、java.lang.management.OperatingSystemMXBean で公開しようぜ、という Issue が OpenJDKに挙がってます。