1. 概要

このチュートリアルでは、JVMにロードされたすべてのクラスを一覧表示するためのさまざまな手法を学習します。 たとえば、JVMのヒープダンプをロードしたり、実行中のアプリケーションをさまざまなツールに接続して、そのツールにロードされたすべてのクラスを一覧表示したりできます。 また、これをプログラムで実行するためのさまざまなライブラリがあります。

非プログラマティックアプローチとプログラマティックアプローチの両方について説明します。

2. 非プログラム的アプローチ

2.1. VM引数の使用

ロードされたすべてのクラスをリストするための最も簡単なアプローチは、コンソールの出力またはファイルにそれを記録することです。

以下のJVM引数を使用してJavaアプリケーションを実行します。

java <app_name> --verbose:class
[Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.io.Serializable from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.Comparable from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.CharSequence from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.String from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.reflect.AnnotatedElement from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.reflect.GenericDeclaration from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.reflect.Type from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.Class from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
...............................

Java 9の場合、 -Xlog JVM引数を使用して、ファイルにロードされたクラスをログに記録します。

java <app_name> -Xlog:class+load=info:classloaded.txt

2.2. ヒープダンプの使用

さまざまなツールがJVMヒープダンプを使用してクラスにロードされた情報を抽出する方法を見ていきます。 ただし、最初に、以下のコマンドを使用してヒープダンプを生成します。

jmap -dump:format=b,file=/opt/tmp/heapdump.bin <app_pid>

上記のヒープダンプは、さまざまなツールで開いて、さまざまなメトリックを取得できます。

Eclipseでは、ヒープダンプファイル heapdump.binEclipseメモリアナライザーにロードし、ヒストグラムインターフェイスを使用します。

次に、Java VisualVMインターフェイスでヒープダンプファイルheapdump.binを開き、インスタンスまたはサイズによるクラスオプションを使用します。

2.3. JProfiler

JProfiler は、さまざまなメトリックを表示するための豊富な機能セットを備えた、トップのJavaアプリケーションプロファイラーの1つです。

JProfiler では、実行中のJVMに接続するか、ヒープダンプファイルをロードして、ロードされたすべてのクラスの名前を含む、JVM関連のすべてのメトリックを取得できます。

アタッチプロセス機能を使用して、JProfilerが実行中のアプリケーションListLoadedClassに接続できるようにします。

次に、アプリケーションのスナップショットを取得し、それを使用してすべてのクラスをロードします。

以下に、ヒープウォーカー機能を使用してロードされたクラスのインスタンス数の名前を示します。

3. プログラマティックアプローチ

3.1. インストルメンテーションAPI

JavaはInstrumentationAPI を提供します。これは、アプリケーションで価値のあるメトリックを取得するのに役立ちます。 まず、Javaエージェントを作成してロードし、Instrumentationインターフェイスのインスタンスをアプリケーションに取得する必要があります。 Javaエージェントは、JVMで実行されているプログラムを計測するためのツールです。

次に、 Instrumentation method getInitiatedClasses(Classloader loader)を呼び出して、特定のクラスローダータイプによってロードされたすべてのクラスを取得する必要があります。

3.2. Google Guava

Guavaライブラリが、現在のクラスローダーを使用してJVMにロードされたすべてのクラスのリストを取得する方法を確認します。

Guava依存関係をMavenプロジェクトに追加することから始めましょう。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

ClassPathオブジェクトを現在のクラスローダーインスタンスで初期化します。

ClassPath classPath = ClassPath.from(ListLoadedClass.class.getClassLoader());
Set<ClassInfo> classes = classPath.getAllClasses();
Assertions.assertTrue(4 < classes.size());

3.3. Reflections API

現在のクラスパスをスキャンして実行時にクエリできるReflectionsライブラリを使用します。

リフレクション依存関係をMavenプロジェクトに追加することから始めましょう。

<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.10.2</version>
</dependency>

次に、パッケージの下にクラスのセットを返すサンプルコードを調べます。

Reflections reflections = new Reflections(packageName, new SubTypesScanner(false));
Set<Class> classes = reflections.getSubTypesOf(Object.class)
  .stream()
  .collect(Collectors.toSet());
Assertions.assertEquals(4, classes.size());

4. 結論

この記事では、JVMにロードされたすべてのクラスを一覧表示するさまざまな方法を学びました。 まず、VM引数を使用して、ロードされたクラスのリストをログに記録する方法を確認しました。

次に、さまざまなツールがヒープダンプをロードする方法、またはJVMに接続して、ロードされたクラスを含むさまざまなメトリックを表示する方法について説明しました。 最後に、Javaライブラリのいくつかについて説明しました。

いつものように、すべてのコードはGitHub利用できます。