Java - получить список всех классов, загруженных в JVM

Я хотел бы получить список всех классов, принадлежащих к определенному пакету, а также всех их детей. Классы могут быть загружены или не загружены в JVM.

13 ответов

Решение

Ну, я просто перечислил все файлы в пути к классам. Возможно, это не великолепное решение, но оно работает надежно и дает мне все, что я хочу, и даже больше.

Это не программное решение, но вы можете запустить

java -verbose:class ....

и JVM выгрузит то, что это загружает, и откуда.

[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]

Смотрите здесь для более подробной информации.

Используя библиотеку Reflections, это просто:

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

Это будет сканировать все классы в url/s, который содержит пакет my.pkg.

  • параметр false означает - не исключайте класс Object, который исключен по умолчанию.
  • в некоторых сценариях (различные контейнеры) вы можете передать classLoader, а также параметр.

Итак, получение всех классов - это эффективное получение всех подтипов Object, транзитивно:

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

(Обычный способ reflections.getSubTypesOf(Object.class) вызовет загрузку всех классов в PermGen и, вероятно, вызовет OutOfMemoryError. ты не хочешь этого делать...)

Если вы хотите получить все прямые подтипы объекта (или любого другого типа) без одновременного получения его транзитивных подтипов, используйте это:

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

Есть несколько ответов на этот вопрос, частично из-за неоднозначного вопроса - в заголовке говорится о классах, загружаемых JVM, тогда как содержание вопроса говорит, что "может или не может быть загружено JVM".

Предполагая, что OP нужны классы, которые загружаются JVM заданным загрузчиком классов, и только эти классы - и мне это тоже нужно - есть решение ( разработанное здесь), которое выглядит следующим образом:

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

}

Одна из приятных вещей в этом заключается в том, что вы можете выбрать произвольный загрузчик классов, который хотите проверить. Тем не менее, он может сломаться, если внутренние компоненты класса загрузчика классов изменятся, поэтому его следует использовать как одноразовый диагностический инструмент.

Я также предлагаю вам написать -javagent агент, но используйте метод getAllLoadedClasses вместо преобразования любых классов.

Для синхронизации с вашим клиентским кодом (нормальный Java-код) создайте сокет и через него свяжитесь с агентом. Затем вы можете вызвать метод "список всех классов", когда вам нужно.

Альтернативный подход к описанному выше заключается в создании внешнего агента с использованием java.lang.instrument чтобы узнать, какие классы загружены и запустить вашу программу с -javaagent переключатель:

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

Этот подход имеет дополнительное преимущество, предоставляя вам информацию о том, какой ClassLoader загрузил данный класс.

Из Oracle Doc вы можете использовать -Xlog опция, которая имеет возможность записи в файл.

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

Есть еще одна возможность использоватьVM.class_hierarchy, доступный начиная с JDK 8 (проверено на 1.8.0_322).

      $ jcmd 44506 VM.class_hierarchy

Это даст такой результат:

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

Запустите свой код под JRockit JVM, затем используйте JRCMD <PID> print_class_summary

Это выведет все загруженные классы, по одному в каждой строке.

Один из способов, если вы уже знаете путь к пакету верхнего уровня, - это использовать OpenPojo

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

Затем вы можете просмотреть список и выполнить любую функцию, какую пожелаете.

Эта программа распечатает все классы со своим физическим путем. Использование может просто скопировать это в любой JSP, если вам нужно проанализировать загрузку классов с любого сервера веб / приложений.

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

Возможно, вы сможете получить список классов, которые загружаются через загрузчик классов, но это не будет включать классы, которые вы еще не загрузили, но находятся в вашем пути к классам.

Чтобы получить ВСЕ классы на вашем пути к классам, вы должны сделать что-то вроде вашего второго решения. Если вы действительно хотите классы, которые в данный момент "загружены" (другими словами, классы, на которые вы уже ссылались, обращались или создавали их экземпляры), вам следует уточнить свой вопрос, чтобы указать на это.

Если вам не нужны никакие библиотеки и вам нужна эта информация во время выполнения, вы можете использовать ее для Java 11+. Он находит все системные модули, загруженные во время выполнения, перебирает их записи (пути) и собирает элементы класса.

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

Если вам нужны несистемные классы, необходимо учитывать некоторые условия. В обоих случаях вы можете использоватьclassссылка на что-то в вашем проекте, чтобы получить необходимый контекст для поиска других классов.

Если вам нужны только загруженные в данный момент классы, вы можете использовать отражение для доступа кVector<Class<?>> classesвClassLoader. Это не будет включать классы из библиотек и еще не инициализированные.

Если вам нужны все классы всех библиотек, которые у вас есть в проекте, вам нужно использовать отражение для доступа кAppClassLoaderхURLClassPath ucp. Это будет держатьArrayList<URL> pathсодержащий URL-адрес, указывающий на каждый каталог/jar/etc, содержащий ссылочные ресурсы. Перемещаясь по ним, вы можете использовать обход пути для сбора имен записей классов, подобных приведенному выше фрагменту кода.

Другие вопросы по тегам