Java-获取 JVM 中加载的所有类的列表

我想得到属于某个包的所有类以及它们的所有子类的列表。这些类可能已经加载到 JVM 中,也可能没有。

119719 次浏览

It's not a programmatic solution but you can run

java -verbose:class ....

and the JVM will dump out what it's loading, and from where.

[Opened /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/sunrsasign.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/jsse.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/jce.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/charsets.jar]
[Loaded java.lang.Object from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.io.Serializable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.lang.CharSequence from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.lang.String from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]

See here for more details.

You might be able to get a list of classes that are loaded through the classloader but this would not include classes you haven't loaded yet but are on your classpath.

To get ALL classes on your classpath you have to do something like your second solution. If you really want classes that are currently "Loaded" (in other words, classes you have already referenced, accessed or instantiated) then you should refine your question to indicate this.

An alternative approach to those described above would be to create an external agent using java.lang.instrument to find out what classes are loaded and run your program with the -javaagent switch:

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;


public class SimpleTransformer implements ClassFileTransformer {


public SimpleTransformer() {
super();
}


public byte[] transform(ClassLoader loader, String className, Class redefiningClass, ProtectionDomain domain, byte[] bytes) throws IllegalClassFormatException {
System.out.println("Loading class: " + className);
return bytes;
}
}

This approach has the added benefit of providing you with information about which ClassLoader loaded a given class.

I'd also suggest you write a -javagent agent, but use the getAllLoadedClasses method instead of transforming any classes.

To synchronize with your client code (Normal Java code), create a socket and communicate with the agent through it. Then you can trigger a "list all classes" method whenever you need.

Well, what I did was simply listing all the files in the classpath. It may not be a glorious solution, but it works reliably and gives me everything I want, and more.

Run your code under a JRockit JVM, then use JRCMD <PID> print_class_summary

This will output all loaded classes, one on each line.

One way if you already know the package top level path is to use OpenPojo

final List<PojoClass> pojoClasses = PojoClassFactory.getPojoClassesRecursively("my.package.path", null);

Then you can go over the list and perform any functionality you desire.

using the Reflections library, it's easy as:

Reflections reflections = new Reflections("my.pkg", new SubTypesScanner(false));

That would scan all classes in the url/s that contains my.pkg package.

  • the false parameter means - don't exclude the Object class, which is excluded by default.
  • in some scenarios (different containers) you might pass the classLoader as well as a parameter.

So, getting all classes is effectively getting all subtypes of Object, transitively:

Set<String> allClasses =
reflections.getStore().getSubTypesOf(Object.class.getName());

(The ordinary way reflections.getSubTypesOf(Object.class) would cause loading all classes into PermGen and would probably throw OutOfMemoryError. you don't want to do it...)

If you want to get all direct subtypes of Object (or any other type), without getting its transitive subtypes all in once, use this:

Collection<String> directSubtypes =
reflections.getStore().get(SubTypesScanner.class).get(Object.class.getName());

There are multiple answers to this question, partly due to ambiguous question - the title is talking about classes loaded by the JVM, whereas the contents of the question says "may or may not be loaded by the JVM".

Assuming that OP needs classes that are loaded by the JVM by a given classloader, and only those classes - my need as well - there is a solution (elaborated here) that goes like this:

import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Vector;


public class CPTest {


private static Iterator list(ClassLoader CL)
throws NoSuchFieldException, SecurityException,
IllegalArgumentException, IllegalAccessException {
Class CL_class = CL.getClass();
while (CL_class != java.lang.ClassLoader.class) {
CL_class = CL_class.getSuperclass();
}
java.lang.reflect.Field ClassLoader_classes_field = CL_class
.getDeclaredField("classes");
ClassLoader_classes_field.setAccessible(true);
Vector classes = (Vector) ClassLoader_classes_field.get(CL);
return classes.iterator();
}


public static void main(String args[]) throws Exception {
ClassLoader myCL = Thread.currentThread().getContextClassLoader();
while (myCL != null) {
System.out.println("ClassLoader: " + myCL);
for (Iterator iter = list(myCL); iter.hasNext();) {
System.out.println("\t" + iter.next());
}
myCL = myCL.getParent();
}
}


}

One of the neat things about it is that you can choose an arbitrary classloader you want to check. It is however likely to break should internals of classloader class change, so it is to be used as one-off diagnostic tool.

This program will prints all the classes with its physical path. use can simply copy this to any JSP if you need to analyse the class loading from any web/application server.

import java.lang.reflect.Field;
import java.util.Vector;


public class TestMain {


public static void main(String[] args) {
Field f;
try {
f = ClassLoader.class.getDeclaredField("classes");
f.setAccessible(true);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Vector<Class> classes =  (Vector<Class>) f.get(classLoader);


for(Class cls : classes){
java.net.URL location = cls.getResource('/' + cls.getName().replace('.',
'/') + ".class");
System.out.println("<p>"+location +"<p/>");
}
} catch (Exception e) {


e.printStackTrace();
}
}
}

List of all Classes loaded in the JVM

From Oracle doc you can use -Xlog option that has a possibility to write into file.

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

There's another possibility using VM.class_hierarchy, available since JDK 8 (tested on 1.8.0_322).

$ jcmd 44506 VM.class_hierarchy

This will give a result like that :

44506:
java.lang.Object/null
|--com.intellij.psi.impl.source.tree.JavaElementType$$Lambda$1163/0x0000000800cd86d8/0x0000600002f012c0
|--com.intellij.ide.IdeTooltipManager/0x0000600002f2c6e0
|--sun.security.ssl.SSLBasicKeyDerivation$SecretSizeSpec/null
|--com.intellij.openapi.editor.impl.view.EditorSizeManager$$Lambda$2094/0x0000000801774c38/0x0000600002f2c6e0
|--com.intellij.psi.util.CachedValueProfiler$EventPlace/0x0000600002f2c6e0 (intf)
|--io.ktor.utils.io.internal.ReadWriteBufferStateKt/0x0000600002fcd680
|--com.intellij.javascript.nodejs.library.core.codeInsight.NodePredefinedReferenceErrorUpdater/0x0000600002f13660
|--com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl$MyAsyncFileListener/0x0000600002f2c6e0
|--java.lang.management.ManagementFactory$PlatformMBeanFinder$$Lambda$89/0x000000080013e0b0/null
|--org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanelProvider$AvailabilityInfo/0x0000600002f0ada0
|  |--org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanelProvider$AvailabilityInfo$2/0x0000600002f0ada0
|  |--org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanelProvider$AvailabilityInfo$1/0x0000600002f0ada0
|--java.lang.invoke.LambdaForm$DMH/0x000000080012a800/null
|--git4idea.status.GitStagingAreaHolder$$Lambda$2907/0x0000000801d2e690/0x0000600002f41cc0
|--com.intellij.lang.javascript.refactoring.extractSuper.JSCustomExtractInterfaceHandler/0x0000600002f13660 (intf)
|--javax.xml.transform.stream.StreamSource/null
...

If you don't want any libraries and need this information given to you at runtime, you can use this for Java 11+. It finds all system modules loaded at runtime, iterates over their entries (path names) and collects class items.

public static List<String> getSystemClasses() {
// Errorables is a util class to ignore exceptions on lambda types, easy enough to implement yourself
return ModuleFinder.ofSystem().findAll().stream()
.map(modRef -> Errorables.silent(modRef::open)) // open reader to each module
.flatMap(modReader -> Errorables.silent(modReader::list)) // list all items in the module
.filter(s -> s.endsWith(".class") && s.indexOf('-') == -1) // retain only classes (except module-info or package-info)
.map(s -> s.substring(0, s.length() - 6)) // cut off '.class' from the path
.collect(Collectors.toList());
}

If you need non-system classes then there are some conditions to consider. In both situations you can use a class reference to something in your project to get the needed context to find other classes.

If you want only the currently loaded classes, you can use reflection to access the Vector<Class<?>> classes in ClassLoader. This will not include classes from libraries and such that have not yet been initialized.

screenshot of only the initialized classes

If you want all classes of all libraries you have in a project then you'll want to use reflection to access the AppClassLoader's URLClassPath ucp. This will hold a ArrayList<URL> path containing URL's pointing to every directory/jar/etc holding referenced resources. Navigating those you can use path-walking to collect the names of class entries similar to the code snippet above.

screenshot of file path uris to referenced libraries