首先看一下下面兩段代碼有什么區(qū)別:
{
new A().test();
}
和
{
A a = new A();
a.test();
}
當(dāng)時去問我們項目經(jīng)理,他堅持認(rèn)為這兩種方式是一樣的崭孤,個人習(xí)慣不同造成的不同寫法而已糊肠。就功能上來說,都調(diào)用了test()函數(shù)货裹,確實沒什么區(qū)別弧圆,但是笔咽,如果考慮了內(nèi)存回收叶组,這兩種寫法就有很大的不同扶叉。
我們可以把這個例子更具體一點帕膜,如下:
{
//mark 1
new A().test();
//mark 2
new A().test();
//mark 3
.....
}
第一種寫法垮刹,在mark2處荒典,A的內(nèi)存已經(jīng)可以被交給垃圾回收器回收了,也就是說在mark2處覆糟,可用內(nèi)存和mark1處的可用內(nèi)存完全相同遮咖。
{
//mark 1
A a = new A();
//mark 2
A b = new A();
a.test();
b.test();
}
第二種寫法在mark 2處的可用內(nèi)存和mark1處的可用內(nèi)存是不同的,如果A類使用很大的空間麦箍,那么在mark2這里會拋出內(nèi)存溢出異常陶珠,相反揍诽,第一種寫法卻沒有這種問題暑脆。
下面的測試代碼證明了兩種寫法的區(qū)別
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的內(nèi)存回收機(jī)制根资,當(dāng)java發(fā)現(xiàn)可用內(nèi)存不足時,會調(diào)用內(nèi)存回收器部脚,內(nèi)存回收器會去遍歷當(dāng)前線程棧委刘,然后根據(jù)棧中的引用確定當(dāng)前被使用的內(nèi)存鹰椒,將沒有被遍歷到的內(nèi)存釋放,在上面的例子中淆珊,b處于棧上施符,無法被回收戳吝,因此在c申請新內(nèi)存是異常听哭。b和c指向的內(nèi)存要等到出了作用域(最近的大括號)才可以被回收柬采。
這個問題解決后,馬上又有一個新的問題粉捻,第一種寫法中我們調(diào)用 new A().test(); 如果這個函數(shù)執(zhí)行時間非常長礁遣,如何保證在執(zhí)行過程中A的內(nèi)存不會被回收(沒有顯式處于棧上的引用指向)。
考慮到c++的臨時變量肩刃,所以猜想java的編譯器會將new A().test();這段代碼做如下處理:
{
{
//mark 1
A temp = new A();
temp.test();
}
//mark 2
}
在mark1處祟霍,從棧上分配temp引用指向堆中的A,之后盈包,在mark2處沸呐,由于temp離開他自己的作用域,則棧上內(nèi)存釋放呢燥,也就是說棧上不再具有指向A的引用崭添,使得A內(nèi)存可被回收。
結(jié)論:
推薦使用 new A().test();這樣的寫法叛氨,在一定程度上可以節(jié)省當(dāng)前內(nèi)存呼渣。
(原文時間2013-1-30)