内部类与静态嵌套类的GC性能受到影响

时间:2013-12-04 16:25:04

标签: java garbage-collection nested-class

我刚刚遇到了一个奇怪的效果,在跟踪它时,我注意到收集内部嵌套类和静态嵌套类似乎有很大的性能差异。请考虑以下代码片段:

public class Test {
    private class Pointer {
        long data;
        Pointer next;
    }
    private Pointer first;

    public static void main(String[] args) {
        Test t = null;
        for (int i = 0; i < 500; i++) {
            t = new Test();
            for (int j = 0; j < 1000000; j++) {
                Pointer p = t.new Pointer();
                p.data = i*j;
                p.next = t.first;
                t.first = p;
            }
        }
    }
}

所以代码的作用是使用内部类创建链表。该过程重复500次(用于测试目的),丢弃上次运行中使用的对象(受GC影响)。

以严格的内存限制(例如100 MB)运行时,此代码在我的计算机上执行大约需要20分钟。现在,通过简单地用静态嵌套类替换内部类,我可以将运行时减少到不到6分钟。以下是更改:

    private static class Pointer {

                Pointer p = new Pointer();

现在我从这个小实验得出的结论是,使用内部类会使GC更难以确定是否可以收集对象,在这种情况下使静态嵌套类的速度提高3倍以上。

我的问题是这个结论是否正确;如果是的话是什么原因,如果没有为什么内部课程这么慢?

2 个答案:

答案 0 :(得分:16)

我认为这是由于两个因素造成的。你已经触及过的第一个。第二种是使用非静态内部类导致更多的内存使用。你为什么问?因为非静态内部类也可以访问其包含类的数据成员和方法,这意味着您正在分配一个基本上扩展超类的Pointer实例。对于非静态内部类,您不会扩展包含类。这是我正在谈论的一个例子

Test.java(非静态内部类)

public class Test {
    private Pointer first;

    private class Pointer {
        public Pointer next;
        public Pointer() {
            next = null;
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        Pointer[] p = new Pointer[1000];
        for ( int i = 0; i < p.length; ++i ) {
            p[i] = test.new Pointer();
        }

        while (true) {
            try {Thread.sleep(100);}
            catch(Throwable t) {}
        }
    }
}

Test2.java(静态内部类)

public class Test2 {
    private Pointer first;

    private static class Pointer {
        public Pointer next;
        public Pointer() {
            next = null;
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        Pointer[] p = new Pointer[1000];
        for ( int i = 0; i < p.length; ++i ) {
            p[i] = new Pointer();
        }

        while (true) {
            try {Thread.sleep(100);}
            catch(Throwable t) {}
        }
    }
}

当两者都运行时,您可以看到非静态占用的堆空间比静态占用的多。具体来说,非静态版本使用2,279,624 B,静态版本使用 10,485,760 1,800,000 B。

因此,它归结为非静态内部类使用更多内存,因为它包含对包含类的引用(至少)。静态内部类不包含此引用,因此永远不会为其分配内存。通过将堆大小设置得如此之低,您实际上是在颠倒堆,从而导致3倍的性能差异。

答案 1 :(得分:7)

当您接近最大堆大小(-Xmx)时,垃圾收集的成本非常非线性地增加,其中JVM最终放弃并且抛出OutOfMemoryError,其接近无限的人为限制。在这种特殊情况下,您会看到该曲线的陡峭部分位于内部类之间是静态的还是非静态的。除了使用更多内存和拥有更多链接之外,非静态内部类并不是真正的原因。我已经看到许多其他的代码更改“导致”GC捶打,他们恰好是将它推到边缘的不幸的闷棍,并且堆限制应该简单地设置得更高。这种非线性行为不应该通常被认为是代码的问题 - 它是JVM固有的。

当然,另一方面,膨胀是膨胀的。在目前的情况下,一个好习惯是使内部类“默认”为静态,除非访问外部实例是有用的。