Почему я получаю исключение NoClassDefFoundError, а не ошибку StackOverflow?

Игра с Java (v9 специально) Я нашел эту ситуацию:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

interface A {
    static A staticMethod() {
        try {
            Method method = A.class.getDeclaredMethods()[0];
            return (A) method.invoke(null);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

public class Test {
    public static void main(String[] args) {
        A.staticMethod();
    }
}

Этот поток программы должен вызывать ошибку StackOverflow, однако я получаю NoClassDefFoundError.

*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 880
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 880
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 880
Exception in thread "main" 
Exception: java.lang.NoClassDefFoundError thrown from the UncaughtExceptionHandler in thread "main"

Согласно Javadoc

Класс NoClassDefFoundError

Брошено, если виртуальная машина Java или экземпляр ClassLoader пытается загрузить в определении класса (как часть обычного вызова метода или как часть создания нового экземпляра с использованием нового выражения) и нет определения класс можно найти.

Искаженное определение класса существовало при компиляции текущего исполняемого класса , но определение больше не может быть найдено.

Это странное сообщение об ошибке, это ошибка?

ОБНОВЛЕНИЕ: Идентификатор отчета об ошибке: 9052375

Выполняется из командной строки и печатает ожидаемую ошибку: Проблема заключалась в том, что исключения использовались в catch.

введите описание изображения здесь

Ответ 1

Это не ошибка, и она также не имеет ничего общего со статическими методами в интерфейсах.

Сообщение java.lang.instrument ASSERTION FAILED также не имеет значения и является просто артефактом запуска кода из IDE. Выполнение того же класса из командной строки приведет только к Exception in thread "main".

Давайте упростим ваш пример до

public class Test {
    public static void main( String[] args ) throws Exception {
        recursive();
    }

    public static void recursive() throws Exception {
        try {
            Test.class
                    .getDeclaredMethod( "recursive" )
                    .invoke( null );
        } catch ( InvocationTargetException e ) {
            e.printStackTrace();
        }
    }
}

Что происходит:

  • Рекурсивный метод вызывает StackOverflowError, как и ожидалось.
  • StackOverflowError заключен в InvocationTargetException, который вызывается из самого глубокого вложенного вызова method.invoke().
  • InvocationTargetException сразу же пойман, и JVM пытается выполнить printStackTrace(), но для этого ему нужно загрузить некоторые классы. Но помните, что на этом этапе стек исчерпан, и любые нетривиальные методы снова попадут в StackOverflowError, что именно происходит где-то внутри загрузчика классов, когда он пытается загрузить некоторый класс, необходимый для печати трассировки стека. Класс загрузчик нашел класс, но не смог загрузить и инициализировать его, и он сообщает, что как NoClassDefFoundError.

Следующий код докажет, что InvocationTargetException действительно обертывает StackOverflowError:

public class Test {
    public static void main( String[] args ) throws Exception {
        recursive();
    }

    public static void recursive() throws Exception {
        try {
            Test.class
                    .getDeclaredMethod( "recursive" )
                    .invoke( null );
        } catch ( InvocationTargetException e ) {
            System.out.println(e);
            System.out.println(e.getTargetException());
        }
    }
}

И следующий код докажет, что если классы, необходимые для выполнения printStackTrace(), уже загружены, тогда код ведет себя как ожидалось (печатает трассировку стека для InvocationTargetException, вызванную StackOverflowError:

public class Test {
    public static void main( String[] args ) throws Exception {
        new Exception().printStackTrace(); // initialize all required classes
        recursive();
    }

    public static void recursive() throws Exception {
        try {
            Test.class
                    .getDeclaredMethod( "recursive" )
                    .invoke( null );
        } catch ( InvocationTargetException e ) {
            e.printStackTrace();
        }
    }
}

Открытый вопрос заключается в том, почему API отражения обрабатывает StackOverflowError вообще, а не просто завершает всю цепочку вызовов с ошибкой.