List All Classes Loaded in a Specific Class Loader

1. Overview

In this tutorial, we'll analyze the technique to list all the classes loaded by a specific class loader in Java, using the Java Instrumentation API. We'll also see how to create and load a Java agent to acquire an Instrumentation instance and invoke the required methods to accomplish our task.

2. Class Loaders in Java

The class loaders are an integral part of the JRE (Java Runtime Environment). Their job is to dynamically load classes into the Java Virtual Machine. In other words, they load classes into memory on-demand when required by the application. The article on Java class loaders talks about their different types and provides a detailed understanding of how they work.

3. Using the Instrumentation API

The Instrumentation interface provides the getInitiatedClasses(Classloader loader) method that can be invoked to return an array comprising all the classes loaded by the particular loader. Let's see how this works.

First, we need to create and load an agent to acquire an instance of the Instrumentation interface. A Java agent is a tool to instrument programs running on the JVM (Java Virtual Machine).

In other words, it can add or modify the bytecode of methods for the purpose of gathering data. We'll require an agent to get a handle on the Instrumentation instance and invoke the required method.

There are multiple ways to create and load an agent. In this tutorial, we'll use the static loading approach using the premain method and the -javaagent option.

3.1. Creating a Java Agent

To create a Java agent, we need to define the premain method to which the Instrumentation instance will be passed on agent load. Let's now create the ListLoadedClassesAgent class:

public class ListLoadedClassesAgent {

private static Instrumentation instrumentation;

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

3.2. Defining the listLoadedClasses Methods

In addition to defining the agent, we'll define and expose a static method to return an array of loaded classes for a given class loader.

Note that if we pass a class loader with a null value to the getInitiatedClasses method, it returns the classes loaded by the bootstrap class loader.

Let's see the code in action:

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;
}

Note that if we're using Java 9 or above, we can use the getPlatformClassLoader method. This will list the classes loaded by the Platform class loader. In that case, the switch case will also contain:

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

3.3. Creating the Agent Manifest File

Now, let's create a manifest file, MANIFEST.MF, with appropriate attributes for our agent to run, including:

Premain-Class: com.baeldung.loadedclasslisting.ListLoadedClassesAgent

The complete list of manifest attributes for an agent JAR file is available on the official documentation of the java.lang.instrument package.

3.4. Loading the Agent and Running the Application

Let's now load the agent and run the application. First, we need the agent JAR file with a manifest file containing the Premain-Class information. Additionally, we need the application JAR file with a manifest file containing the Main-Class information. The Launcher class containing the main method will start our application. Then we'll be able to print the classes loaded by different types of class loaders:

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()));
}
}

Next, let's statically load the Java agent and start our application:

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

After running the above command, we'll see the output:

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. Conclusion

In this tutorial, we learned about the technique to list all the classes loaded in a specific class loader.

First, we created the Java Agent. After that, we defined the method to list the loaded classes using the Java Instrumentation API. Finally, we created the agent manifest files, loaded the agent, and ran our application.

As always, the complete source code of the example can be found over on GitHub.

Reference: