我来编写以下代码:
public class foo {
static int iterationCounter = 0;
public foo() {
iterationCounter++;
System.out.println(iterationCounter);
new foo();
}
public static void main(String[] args) {
new foo();
}
}
在生成StackOverflow Exception之前,由值iterationCounter
组成的最后一个日志是:11472
,因此Java留出x
的内存量来创建11472
foo对象。
然而,以下代码输出的日志与其他程序的日志不同:
public class foo {
static int iterationCounter = 0;
foo fooObject;
public foo() {
iterationCounter++;
System.out.println(iterationCounter);
this.fooObject = new foo();
}
public static void main(String[] args) {
new foo();
}
}
这是我在记忆管理方面的困惑。我以为iterationCounter
的值会和其他程序的值一样,但这次的值是9706
。由于fooObject
是一个公共变量(一个字段),它应该存储在堆内存中(不是这样?),而不是堆栈内存中。如果是这种情况,它不应该占用堆栈的空间(或者将所有新创建的fooObjects及其所有属性存储在堆栈中)?
第一个版本生成以下代码(javap -c ...
的输出):
...
18: invokevirtual #4; //Method java/io/PrintStream.println:(I)V
21: new #5; //class Test
24: dup
25: invokespecial #6; //Method "<init>":()V
28: pop
29: return
第二个-如下:
...
18: invokevirtual #4; //Method java/io/PrintStream.println:(I)V
21: aload_0
22: new #5; //class Test
25: dup
26: invokespecial #6; //Method "<init>":()V
29: putfield #7; //Field test:LTest;
32: return
正如您所看到的,在递归调用之前,这些清单之间的唯一区别是第二个清单第21行的aload_0
。
该操作将局部变量0
(它是this
)的值加载到堆栈上,以便稍后可以通过putfield
操作将其用作对象引用。
因此,您观察到的差异是由于堆栈上每次调用都有一个额外的条目——用于将值写入字段的this
引用。
因此Java留出x内存量来创建11472个foo对象。
对象是在堆上分配的,您不会用完这些对象。您将得到一个OutOfMemoryError。
您将用完的是带有StackOverflowError的堆栈。由于您没有本地变量,所以您在堆栈上使用的只是保存需要返回的状态,因此您的深度相对较高。
我原以为迭代计数器的值会和其他程序的值一样,但这次的值是9706
很可能你实际上有
foo $local_variable$ = new foo();
this.fooObject = $local_variable$
这意味着在每次递归中使用更多的堆栈(多引用一次)