1. 概要

このチュートリアルでは、 Java Instrumentation API を使用して、の手法を分析し、Javaの特定のクラスローダーによってロードされたすべてのクラスを一覧表示します。 また、Javaエージェントを作成してロードし、 Instrumentation インスタンスを取得し、タスクを実行するために必要なメソッドを呼び出す方法についても説明します。

2. Javaのクラスローダー

クラスローダーは、JRE(Javaランタイム環境)の不可欠な部分です。 彼らの仕事は、クラスをJava仮想マシンに動的にロードすることです。 つまり、アプリケーションで必要になったときに、オンデマンドでクラスをメモリにロードします。 Javaクラスローダーに関する記事では、さまざまなタイプについて説明し、それらがどのように機能するかについて詳細に理解しています。

3. InstrumentationAPIを使用する

Instrumentation インターフェースは、 getInitiatedClasses(Classloader loader)メソッドを提供します。このメソッドは呼び出して 、特定のローダーによってロードされたすべてのクラスを含む配列を返します[ X218X]。 これがどのように機能するか見てみましょう。

まず、 Instrumentation インターフェースのインスタンスを取得するために、エージェントを作成してロードする必要があります。 Javaエージェントは、JVM(Java仮想マシン)で実行されているプログラムを計測するためのツールです。

つまり、データを収集する目的でメソッドのバイトコードを追加または変更できます。 Instrumentation インスタンスのハンドルを取得し、必要なメソッドを呼び出すには、agentが必要です。

エージェントを作成してロードするには複数の方法があります。 このチュートリアルでは、premainメソッドと-javaagentオプションを使用した静的ロードアプローチを使用します。

3.1. Javaエージェントの作成

Javaエージェントを作成するには、エージェントのロードでInstrumentationインスタンスが渡される premainメソッドを定義する必要があります。 次に、ListLoadedClassesAgentクラスを作成しましょう。

public class ListLoadedClassesAgent {

    private static Instrumentation instrumentation;

    public static void premain(String agentArgs, Instrumentation instrumentation) {
        ListLoadedClassesAgent.instrumentation = instrumentation;
    }
}

3.2. listLoadedClassesメソッドの定義

エージェントの定義に加えて、静的メソッドを定義して公開し、特定のクラスローダーのロードされたクラスの配列を返します。

null値のクラスローダーをgetInitiatedClassesメソッドに渡すと、ブートストラップクラスローダーによってロードされたクラスが返されることに注意してください。

実際のコードを見てみましょう。

public static Class<?>[] listLoadedClasses(String classLoaderType) {
    return instrumentation.getInitiatedClasses(
      getClassLoader(classLoaderType));
}

private static ClassLoader getClassLoader(String classLoaderType) {
    ClassLoader classLoader = null;
    switch (classLoaderType) {
        case "SYSTEM":
            classLoader = ClassLoader.getSystemClassLoader();
            break;
        case "EXTENSION":
            classLoader = ClassLoader.getSystemClassLoader().getParent();
            break;
        case "BOOTSTRAP":
            break;
        default:
            break;
    }
    return classLoader;
}

Java 9以降を使用している場合は、getPlatformClassLoaderメソッドを使用できることに注意してください。 これにより、Platformクラスローダーによってロードされたクラスが一覧表示されます。 その場合、スイッチケースには次のものも含まれます。

case "PLATFORM":
    classLoader = ClassLoader.getPlatformClassLoader();
    break;

3.3. エージェントマニフェストファイルの作成

次に、エージェントが実行する適切な属性を含むマニフェストファイルMANIFEST.MFを作成しましょう。

Premain-Class: com.baeldung.loadedclasslisting.ListLoadedClassesAgent

エージェントJARファイルのマニフェスト属性の完全なリストは、java.lang.instrumentパッケージの公式ドキュメントにあります。

3.4. エージェントのロードとアプリケーションの実行

エージェントをロードして、アプリケーションを実行してみましょう。 まず、Premain-Class情報を含むマニフェストファイルを含むエージェントJARファイルが必要です。 さらに、Main-Class情報を含むマニフェストファイルを含むアプリケーションJARファイルが必要です。 mainメソッドを含むLauncherクラスがアプリケーションを開始します。 次に、さまざまなタイプのクラスローダーによってロードされたクラスを印刷できるようになります。

public class Launcher {

    public static void main(String[] args) {
        printClassesLoadedBy("BOOTSTRAP");
        printClassesLoadedBy("SYSTEM");
        printClassesLoadedBy("EXTENSION");
    }

    private static void printClassesLoadedBy(String classLoaderType) {
        System.out.println(classLoaderType + " ClassLoader : ");
        Class<?>[] classes = ListLoadedClassesAgent.listLoadedClasses(classLoaderType);
        Arrays.asList(classes)
            .forEach(clazz -> System.out.println(clazz.getCanonicalName()));
    }
}

次に、静的に Javaエージェントをロードして、アプリケーションを起動しましょう。

java -javaagent:agent.jar -jar app.jar

上記のコマンドを実行すると、次の出力が表示されます。

BOOTSTRAP ClassLoader :
java.lang.ClassValue.Entry[]
java.util.concurrent.ConcurrentHashMap.Segment
java.util.concurrent.ConcurrentHashMap.Segment[]
java.util.StringTokenizer
..............
SYSTEM ClassLoader : 
java.lang.Object[]
java.lang.Object[][]
java.lang.Class
java.lang.Class[]
..............
EXTENSION ClassLoader :
byte[]
char[]
int[]
int[][]
short[]

4. 結論

このチュートリアルでは、特定のクラスローダーにロードされているすべてのクラスを一覧表示する手法について学習しました。

まず、Javaエージェントを作成しました。 その後、Java Instrumentation APIを使用して、ロードされたクラスを一覧表示するメソッドを定義しました。 最後に、エージェントマニフェストファイルを作成し、エージェントをロードして、アプリケーションを実行しました。

いつものように、例の完全なソースコードは、GitHubにあります。