首先看一下下面两段代码有什么区别:
{
new A().test();
}
和
{
A a = new A();
a.test();
}
当时去问我们项目经理,他坚持认为这两种方式是一样的,个人习惯不同造成的不同写法而已。就功能上来说,都调用了test()函数,确实没什么区别,但是,如果考虑了内存回收,这两种写法就有很大的不同。
我们可以把这个例子更具体一点,如下:
{
//mark 1
new A().test();
//mark 2
new A().test();
//mark 3
.....
}
第一种写法,在mark2处,A的内存已经可以被交给垃圾回收器回收了,也就是说在mark2处,可用内存和mark1处的可用内存完全相同。
{
//mark 1
A a = new A();
//mark 2
A b = new A();
a.test();
b.test();
}
第二种写法在mark 2处的可用内存和mark1处的可用内存是不同的,如果A类使用很大的空间,那么在mark2这里会抛出内存溢出异常,相反,第一种写法却没有这种问题。
下面的测试代码证明了两种写法的区别
class MemoryTest
{
int a[] = new int[10 * 1024 * 1024 * 10];
static int b = 0;
MemoryTest()
{
b++;
a[0] = a[1] = 2;
}
void Test()
{
System.out.println("12345 + " + b);
}
}
public class TestJava
{
public static void main(String[] args)
throws Exception
{
//works well
new MemoryTest().Test();
//the gc collected the memory so it can be reuse
new MemoryTest().Test();
MemoryTest c = new MemoryTest();
//if cancel this comment, there will be a memory exception
//that means there's not enough memory for d
/*MemoryTest d = new MemoryTest();*/
System.out.println("end test");
}
}
造成这种问题,主要还是java的内存回收机制,当java发现可用内存不足时,会调用内存回收器,内存回收器会去遍历当前线程栈,然后根据栈中的引用确定当前被使用的内存,将没有被遍历到的内存释放,在上面的例子中,b处于栈上,无法被回收,因此在c申请新内存是异常。b和c指向的内存要等到出了作用域(最近的大括号)才可以被回收。
这个问题解决后,马上又有一个新的问题,第一种写法中我们调用 new A().test(); 如果这个函数执行时间非常长,如何保证在执行过程中A的内存不会被回收(没有显式处于栈上的引用指向)。
考虑到c++的临时变量,所以猜想java的编译器会将new A().test();这段代码做如下处理:
{
{
//mark 1
A temp = new A();
temp.test();
}
//mark 2
}
在mark1处,从栈上分配temp引用指向堆中的A,之后,在mark2处,由于temp离开他自己的作用域,则栈上内存释放,也就是说栈上不再具有指向A的引用,使得A内存可被回收。
结论:
推荐使用 new A().test();这样的写法,在一定程度上可以节省当前内存。
(原文时间2013-1-30)