编程语言
首页 > 编程语言> > java.lang.reflect.Proxy:巨大的异常堆栈跟踪

java.lang.reflect.Proxy:巨大的异常堆栈跟踪

作者:互联网

这是一个简单的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

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

解决方法:

我对此堆栈跟踪的解释是:

>当堆栈溢出最终发生时,抛出了StackOverflowError
>该错误被method.invoke()捕获,并转换为InvocationTargetException(根据it’s javadoc),原始异常是原因
>由于此异常是已检查的异常,因此代理服务器无法让其自行掉落,因此它捕获了该异常并将其转换为UndeclaredThrowableException,再次将其作为原因

这样,对于堆栈溢出之前的每个递归级别,您都会得到另外两个“由”异常以及堆栈跟踪所引起的大量输出.至于输出多少,我们来估算一下:

>每个帖子约有2000次调用
>在每次调用中,堆栈跟踪似乎增加了5行
>每次调用均打印2个错误和堆栈跟踪

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

标签:dynamic-proxy,reflection,exception,stack-trace,java
来源: https://codeday.me/bug/20191119/2032829.html