【问题标题】:java.lang.reflect.Proxy: Huge exception stack tracejava.lang.reflect.Proxy:巨大的异常堆栈跟踪
【发布时间】:2016-06-01 09:53:58
【问题描述】:

这是一个简单的 Java 应用程序:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Main {

    interface MyInterface {
        void myMethod();
    }

    public static void main(String[] args) {
        MyInterface myInterface = (MyInterface) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[] {MyInterface.class},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    return method.invoke(proxy, args);
                }
            });
        myInterface.myMethod();
    }
}

我希望在这里得到一个简单的StackOverflowError,因为我在代理实例上递归调用相同的方法。

但是,异常产生的堆栈跟踪包含数百万行和数百 MB 大小。

堆栈跟踪的第一部分是这样开始的:

java.lang.reflect.UndeclaredThrowableException
    at $Proxy0.myMethod(Unknown Source)
    at Main.main(Main.java:22)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at Main$1.invoke(Main.java:18)
    ... 2 more
Caused by: java.lang.reflect.UndeclaredThrowableException
    at $Proxy0.myMethod(Unknown Source)
    ... 7 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at Main$1.invoke(Main.java:18)
    ... 8 more

并扩展到:

Caused by: java.lang.reflect.UndeclaredThrowableException
    at $Proxy0.myMethod(Unknown Source)
    ... 1022 more

然后遵循数百万行,例如:

Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at Main$1.invoke(Main.java:18)
    at $Proxy0.myMethod(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at Main$1.invoke(Main.java:18)
    .......

和:

Caused by: java.lang.reflect.UndeclaredThrowableException
    at $Proxy0.myMethod(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at Main$1.invoke(Main.java:18)
    at $Proxy0.myMethod(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at Main$1.invoke(Main.java:18)
    .......

最后打印的行是(在上面的重复序列之后):

Exception: java.lang.StackOverflowError thrown from the UncaughtExceptionHandler in thread "main"

我怀疑堆栈跟踪生成机制正在调用代理实例上的某些方法(可能是toString 来打印它,或其他东西),因此一遍又一遍地重复递归(因为代理上的每个方法调用都会导致无限递归),但代理方法执行的总数为 1919(通过增加 InvocationHandler.invoke 方法中的计数器来测量)。

在我的实际用例中,我当然修复了无限递归问题;我只是好奇是否有人知道这是一些错误还是有合理的解释?

Java 版本:

java version "1.8.0_65"
Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.65-b01, mixed mode)

编辑

@JiriTousek 和@AndrewWilliamson 好心地分析了这可能是什么原因。我根据他们的输入实现了一个模拟:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;

public class Test {
    private static int counter = 0;

    public static void main(String[] args) {
        proxy();
    }

    private static void proxy() {
        try {
            methodInvoke();
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    private static void methodInvoke() throws InvocationTargetException {
        try {
            myMethod();
        } catch (Throwable e) {
            throw new InvocationTargetException(e);
        }
    }

    private static void myMethod() {
        if (counter++ == 5) {
            throw new StackOverflowError();
        }
        proxy();
    }
}

这将导致以下堆栈跟踪:

Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
    at Test.proxy(Test.java:16)
    at Test.main(Test.java:9)
Caused by: java.lang.reflect.InvocationTargetException
    at Test.methodInvoke(Test.java:24)
    at Test.proxy(Test.java:14)
    ... 1 more
Caused by: java.lang.reflect.UndeclaredThrowableException
    at Test.proxy(Test.java:16)
    at Test.myMethod(Test.java:32)
    at Test.methodInvoke(Test.java:22)
    ... 2 more
Caused by: java.lang.reflect.InvocationTargetException
    at Test.methodInvoke(Test.java:24)
    at Test.proxy(Test.java:14)
    ... 4 more
Caused by: java.lang.reflect.UndeclaredThrowableException
    at Test.proxy(Test.java:16)
    at Test.myMethod(Test.java:32)
    at Test.methodInvoke(Test.java:22)
    ... 5 more
Caused by: java.lang.reflect.InvocationTargetException
    at Test.methodInvoke(Test.java:24)
    at Test.proxy(Test.java:14)
    ... 7 more
Caused by: java.lang.reflect.UndeclaredThrowableException
    at Test.proxy(Test.java:16)
    at Test.myMethod(Test.java:32)
    at Test.methodInvoke(Test.java:22)
    ... 8 more
Caused by: java.lang.reflect.InvocationTargetException
    at Test.methodInvoke(Test.java:24)
    at Test.proxy(Test.java:14)
    ... 10 more
Caused by: java.lang.reflect.UndeclaredThrowableException
    at Test.proxy(Test.java:16)
    at Test.myMethod(Test.java:32)
    at Test.methodInvoke(Test.java:22)
    ... 11 more
Caused by: java.lang.reflect.InvocationTargetException
    at Test.methodInvoke(Test.java:24)
    at Test.proxy(Test.java:14)
    ... 13 more
Caused by: java.lang.reflect.UndeclaredThrowableException
    at Test.proxy(Test.java:16)
    at Test.myMethod(Test.java:32)
    at Test.methodInvoke(Test.java:22)
    ... 14 more
Caused by: java.lang.reflect.InvocationTargetException
    at Test.methodInvoke(Test.java:24)
    at Test.proxy(Test.java:14)
    ... 16 more
Caused by: java.lang.StackOverflowError
    at Test.myMethod(Test.java:30)
    at Test.methodInvoke(Test.java:22)
    ... 17 more

因此,每个堆栈帧都没有行号增长。

【问题讨论】:

  • 最后的“Caused by”是什么类型的异常?
  • @AndrewWilliamson 已编辑。
  • 我是这么认为的——它最初是堆栈溢出,但invoke 调用将其包装在不同的异常类型中。
  • @AndrewWilliamson 那为什么有数百万行呢?最后的Caused by 不应该只打印大约 1000 行,而包装异常只打印它们的部分跟踪吗?
  • MyInterface.myMethod 没有声明它会抛出任何已检查的异常,但您的实现确实会从method.invoke 抛出。因此,在每个递归级别,都会抛出一个异常,并且您会得到UndeclaredThrowableException 等的整个链。堆栈跟踪中的一行现在变成了 10。

标签: java exception reflection stack-trace dynamic-proxy


【解决方案1】:

我对这个堆栈跟踪的解释是:

  • 最终发生堆栈溢出时,抛出了StackOverflowError
  • 此错误被method.invoke() 捕获并转换为InvocationTargetException(根据it's javadoc),原因是原始异常
  • 由于此异常是已检查异常,因此 Proxy 不能让它通过自己,因此它捕获它并将其转换为 UndeclaredThrowableException,再次以前面的原因作为原因

这样,对于堆栈溢出之前的每个递归级别,您都会得到另外两个“由”异常及其堆栈跟踪 - 大量输出。至于输出多少,我们来猜测一下:

  • 根据您的帖子大约有 2000 次调用
  • 在每次调用中,堆栈跟踪似乎都增加了 5 行
  • 为每次调用打印 2 个错误和堆栈跟踪

所以最大的堆栈跟踪将有大约 10000 行,平均堆栈跟踪将有大约 5000 行,所有内容都打印两次(每种异常类型一次),总计约为 2000 * 5000 * 2 = 2000 万行

【讨论】:

  • 我根据您的分析通过模拟编辑了我的问题。我会说数字不匹配,但我可能仍然缺少一些东西。
  • 看起来在您的模拟中,Java 正在更积极地切断堆栈跟踪。如果您有原始堆栈跟踪,则可以查看底部和顶部以查看单个异常的堆栈跟踪是否在增长。您的模拟还表明,这两个异常长链的原因确实是我所描述的。
  • +1 用于数学分析。实际上,大量打印痕迹的原因是当相同痕迹的深度超过 1024 时,痕迹没有被 ... more 符号截断。我的回答中有更多详细信息。
【解决方案2】:

当相同跟踪的深度大于 1024(默认值)时,打印的堆栈跟踪不是 truncated。这就是为什么最后一个截断的跟踪以... 1022 more 结尾,而所有后续跟踪都完全打印。

可以通过将MaxJavaStackTraceDepth JVM 参数设置为所需的值来更改默认值。当我通过使用-XX:MaxJavaStackTraceDepth=8192 运行它以使用Proxy 为原始示例增加它时,整个打印的堆栈跟踪下降到大约12500 行,以:

Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at Main1$1.invoke(Main1.java:16)
    ... 7003 more
Caused by: java.lang.reflect.UndeclaredThrowableException
    at $Proxy0.myMethod(Unknown Source)
    ... 7007 more

Exception: java.lang.StackOverflowError thrown from the UncaughtExceptionHandler in thread "main"

【讨论】:

    猜你喜欢
    • 2011-01-05
    • 2017-08-06
    • 2010-09-13
    • 1970-01-01
    • 2018-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多