我刚刚遇到了一个奇怪的效果,在跟踪它时,我注意到收集内部嵌套类和静态嵌套类似乎有很大的性能差异。请考虑以下代码片段:
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倍以上。
我的问题是这个结论是否正确;如果是的话是什么原因,如果没有为什么内部课程这么慢?
答案 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固有的。
当然,另一方面,膨胀是膨胀的。在目前的情况下,一个好习惯是使内部类“默认”为静态,除非访问外部实例是有用的。