为异常分配内存

7

您好!请问您能否解释一下,Exception或其子类的实例在内存中是如何分配的?它们是在堆栈中还是其他地方?

谢谢!

4个回答

4
对于大多数JVM,所有对象都是在堆上创建的,异常不例外。 ;)
通过逃逸分析,JVM可以在栈上分配对象,但通常仅限于仅在一个方法中使用而不返回的对象。即异常很可能不是一个好的选择。
许多JVM中Throwables(包括异常)创建的特殊之处在于,堆栈跟踪元素直到需要它们时才会创建。这是因为大部分时间它们不需要,并且它们很昂贵。但是用于创建堆栈跟踪的信息被保留在JVM的某个地方,并与Throwable相关联,但对调试器或反射不可见。
public static void main(String... args) {
    Throwable t = new Throwable("here");
    System.out.println("Throwable before getStackTrace()");
    shallowDump(t);

    System.out.println("\nThrowable after getStackTrace()");
    t.getStackTrace();
    shallowDump(t);
}

private static void shallowDump(Object pojo) {
    for (Field f : pojo.getClass().getDeclaredFields()) {
        if (Modifier.isStatic(f.getModifiers())) continue;
        f.setAccessible(true);
        Object o;
        try {
            o = f.get(pojo);
            if (o == pojo)
                o = "{self}";
            if (o instanceof Object[])
                o = "Array of "+(o.getClass().getComponentType());
        } catch (Exception e) {
            o = e;
        }
        System.out.println(f.getName() + ": " + o);
    }
}

打印

Throwable before getStackTrace()
detailMessage: here
cause: {self}
stackTrace: null

Throwable after getStackTrace()
detailMessage: here
cause: {self}
stackTrace: Array of class java.lang.StackTraceElement

因此,问题出现了,用于创建StackTraceElement的信息保存在哪里。查看代码,使用本地方法访问信息。有一个神秘的字段称为“backtrace”,您无法使用反射看到它。
System.gc();
for (int i = 0; i < 5; i++) {
    Throwable[] ts = new Throwable[10000];
    long free = Runtime.getRuntime().freeMemory();
    for (int j = 0; j < ts.length; j++)
        ts[j] = new Throwable();
    long used = free - Runtime.getRuntime().freeMemory();
    System.out.printf("Average Throwable size was %,d%n", used / ts.length);
}
System.gc();
for (int i = 0; i < 5; i++) {
    Throwable[] ts = new Throwable[10000];
    long free = Runtime.getRuntime().freeMemory();
    for (int j = 0; j < ts.length; j++)
        ts[j] = Throwable.class.newInstance();
    long used = free - Runtime.getRuntime().freeMemory();
    System.out.printf("Average Throwable.class.newInstance() size was %,d%n", used / ts.length);
}

使用反射获取当前方法中创建的Throwable对象和深层方法中创建的Throwable对象的大小(后者具有更深的堆栈)

Average Throwable size was 302
Average Throwable size was 302
Average Throwable size was 302
Average Throwable size was 302
Average Throwable size was 302
Average Throwable.class.newInstance() size was 247
Average Throwable.class.newInstance() size was 296
Average Throwable.class.newInstance() size was 296
Average Throwable.class.newInstance() size was 296
Average Throwable.class.newInstance() size was 296

Throwable的大小比您从它拥有的字段中所预期的要大得多。可以假设一些额外的信息被存储在堆上来帮助这个类,然而,如果所有的信息都存储在Throwable对象中,您会预期第二种类型的Throwable更大。


1
如果JVM想要遵守VM规范,就不能在堆栈上创建对象。堆栈只能保存引用。请参阅JVMS的3.6.1节:http://java.sun.com/docs/books/jvms/second_edition/html/Overview.doc.html#15722 - musiKk
1
@musiKk,我同意编译器无法为虚拟机在虚拟堆栈上分配对象。然而,当JVM编译成本地代码时所做的事情可能会有很大不同。 - Peter Lawrey
我也考虑过逃逸分析,但由于Throwable的字段也是对象,所以可能行不通。请看我的回答。 - BoffinBrain
JVM执行逃逸分析而非编译器。因此,虚拟机的字节码是什么并不重要。 - Peter Lawrey

0
Java中的所有对象默认都分配在堆中。你可以特别地说,异常实例也是如此,因为它们通常被传递给调用者方法,所以它们不可能在栈上。

0

Exception(以及所有的Throwable)与 Java 中的任何其他对象一样。因此,它们会出现在堆中。正如 MusiKk 指出的那样,栈只能保存基本类型值或对象引用。


对象始终位于堆上。只有引用可以在栈上。 - musiKk
在99.9%的情况下,是的... 但是,最新的JDK可以使用逃逸分析来确定对象的引用是否会超出当前范围,如果不会,它根本不会创建对象,并且如果空间允许,直接将原始字段放入堆栈中。然而,Throwable的字段也是对象,所以它们无论如何都必须放在堆上,所以我想我没有完全考虑清楚。我会编辑一下。 - BoffinBrain

0

它们在堆上创建,但这有什么关系呢?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接