Java : 對象不再使用時,為什么要賦值為null衰腌?

前言

許多Java開發(fā)者都曾聽說過“不使用的對象應手動賦值為null“這句話新蟆,而且好多開發(fā)者一直信奉著這句話;問其原因右蕊,大都是回答“有利于GC更早回收內存琼稻,減少內存占用”,但再往深入問就回答不出來了饶囚。

鑒于網(wǎng)上有太多關于此問題的誤導帕翻,本文將通過實例,深入JVM剖析“對象不再使用時賦值為null”這一操作存在的意義萝风,供君參考嘀掸。本文盡量不使用專業(yè)術語,但仍需要你對JVM有一些概念规惰。

示例代碼

我們來看看一段非常簡單的代碼:

publicstaticvoidmain(String[]?args){

if(true)?{

byte[]?placeHolder?=newbyte[64*1024*1024];

System.out.println(placeHolder.length?/1024);

}

System.gc();

}

我們在if中實例化了一個數(shù)組placeHolder睬塌,然后在if的作用域外通過System.gc();手動觸發(fā)了GC,其用意是回收placeHolder,因為placeHolder已經無法訪問到了衫仑。來看看輸出:

65536

[GC68239K->65952K(125952K),0.0014820secs]

[Full?GC65952K->65881K(125952K),0.0093860secs]

Full GC 65952K->65881K(125952K)代表的意思是:本次GC后,內存占用從65952K降到了65881K堕花。意思其實是說GC沒有將placeHolder回收掉文狱,是不是不可思議?

下面來看看遵循“不使用的對象應手動賦值為null“的情況:

publicstaticvoidmain(String[]?args){

if(true)?{

byte[]?placeHolder?=newbyte[64*1024*1024];

System.out.println(placeHolder.length?/1024);

placeHolder?=null;

}

System.gc();

}

其輸出為:

65536

[GC68239K->65952K(125952K),0.0014910secs]

[Full?GC65952K->345K(125952K),0.0099610secs]

這次GC后內存占用下降到了345K缘挽,即placeHolder被成功回收了瞄崇!對比兩段代碼,僅僅將placeHolder賦值為null就解決了GC的問題壕曼,真應該感謝“不使用的對象應手動賦值為null“苏研。

等等,為什么例子里placeHolder不賦值為null腮郊,GC就“發(fā)現(xiàn)不了”placeHolder該回收呢摹蘑?這才是問題的關鍵所在。

運行時棧

典型的運行時棧

如果你了解過編譯原理轧飞,或者程序執(zhí)行的底層機制衅鹿,你會知道方法在執(zhí)行的時候,方法里的變量(局部變量)都是分配在棧上的过咬;當然大渤,對于Java來說,new出來的對象是在堆中掸绞,但棧中也會有這個對象的指針泵三,和int一樣。

比如對于下面這段代碼:

publicstaticvoidmain(String[]?args){

inta?=1;

intb?=2;

intc?=?a?+?b;

}

其運行時棧的狀態(tài)可以理解成:

索引變量

1a

2b

3c

“索引”表示變量在棧中的序號衔掸,根據(jù)方法內代碼執(zhí)行的先后順序烫幕,變量被按順序放在棧中。

再比如:

publicstaticvoidmain(String[]?args){

if(true)?{

inta?=1;

intb?=2;

intc?=?a?+?b;

}

intd?=4;

}

這時運行時棧就是:

索引變量

1a

2b

3c

4d

容易理解吧具篇?其實仔細想想上面這個例子的運行時棧是有優(yōu)化空間的纬霞。

Java的棧優(yōu)化

上面的例子,main()方法運行時占用了4個棧索引空間驱显,但實際上不需要占用這么多诗芜。當if執(zhí)行完后,變量a埃疫、b和c都不可能再訪問到了伏恐,所以它們占用的1~3的棧索引是可以“回收”掉的,比如像這樣:

索引變量

1a

2b

3c

1d

變量d重用了變量a的棧索引栓霜,這樣就節(jié)約了內存空間翠桦。

提醒

上面的“運行時棧”和“索引”是為方便引入而故意發(fā)明的詞,實際上在JVM中销凑,它們的名字分別叫做“局部變量表”和“Slot”丛晌。而且局部變量表在編譯時即已確定,不需要等到“運行時”斗幼。

GC一瞥

這里來簡單講講主流GC里非常簡單的一小塊:如何確定對象可以被回收澎蛛。另一種表達是,如何確定對象是存活的蜕窿。

仔細想想谋逻,Java的世界中,對象與對象之間是存在關聯(lián)的桐经,我們可以從一個對象訪問到另一個對象毁兆。如圖所示。

再仔細想想阴挣,這些對象與對象之間構成的引用關系气堕,就像是一張大大的圖;更清楚一點畔咧,是眾多的樹送巡。

如果我們找到了所有的樹根,那么從樹根走下去就能找到所有存活的對象盒卸,那么那些沒有找到的對象骗爆,就是已經死亡的了!這樣GC就可以把那些對象回收掉了蔽介。

現(xiàn)在的問題是摘投,怎么找到樹根呢?JVM早有規(guī)定虹蓄,其中一個就是:棧中引用的對象犀呼。也就是說,只要堆中的這個對象薇组,在棧中還存在引用外臂,就會被認定是存活的。

提醒

上面介紹的確定對象可以被回收的算法律胀,其名字是“可達性分析算法”宋光。

JVM的“bug”

我們再來回頭看看最開始的例子:

publicstaticvoidmain(String[]?args){

if(true)?{

byte[]?placeHolder?=newbyte[64*1024*1024];

System.out.println(placeHolder.length?/1024);

}

System.gc();

}

看看其運行時棧:

LocalVariableTable:

StartLengthSlotNameSignature

0210args???[Ljava/lang/String;

5??????12?????1?placeHolder???[B

棧中第一個索引是方法傳入?yún)?shù)args,其類型為String[]炭菌;第二個索引是placeHolder罪佳,其類型為byte[]。

聯(lián)系前面的內容黑低,我們推斷placeHolder沒有被回收的原因:System.gc();觸發(fā)GC時赘艳,main()方法的運行時棧中,還存在有對args和placeHolder的引用,GC判斷這兩個對象都是存活的蕾管,不進行回收枷踏。也就是說,代碼在離開if后掰曾,雖然已經離開了placeHolder的作用域呕寝,但在此之后,沒有任何對運行時棧的讀寫婴梧,placeHolder所在的索引還沒有被其他變量重用,所以GC判斷其為存活客蹋。

為了驗證這一推斷塞蹭,我們在System.gc();之前再聲明一個變量,按照之前提到的“Java的棧優(yōu)化”讶坯,這個變量會重用placeHolder的索引番电。

publicstaticvoidmain(String[]?args){

if(true)?{

byte[]?placeHolder?=newbyte[64*1024*1024];

System.out.println(placeHolder.length?/1024);

}

intreplacer?=1;

System.gc();

}

看看其運行時棧:

LocalVariableTable:

StartLengthSlotNameSignature

0230args???[Ljava/lang/String;

5??????12?????1?placeHolder???[B

19???????4?????1?replacer???I

不出所料,replacer重用了placeHolder的索引辆琅。來看看GC情況:

65536

[GC68239K->65984K(125952K),0.0011620secs]

[Full?GC65984K->345K(125952K),0.0095220secs]

placeHolder被成功回收了漱办!我們的推斷也被驗證了。

再從運行時棧來看婉烟,加上int replacer = 1;和將placeHolder賦值為null起到了同樣的作用:斷開堆中placeHolder和棧的聯(lián)系娩井,讓GC判斷placeHolder已經死亡。

現(xiàn)在算是理清了“不使用的對象應手動賦值為null“的原理了似袁,一切根源都是來自于JVM的一個“bug”:代碼離開變量作用域時洞辣,并不會自動切斷其與堆的聯(lián)系。為什么這個“bug”一直存在昙衅?你不覺得出現(xiàn)這種情況的概率太小了么扬霜?算是一個tradeoff了。

總結

希望看到這里你已經明白了“不使用的對象應手動賦值為null“這句話背后的奧義而涉。我比較贊同《深入理解Java虛擬機》作者的觀點:在需要“不使用的對象應手動賦值為null“時大膽去用著瓶,但不應當對其有過多依賴,更不能當作是一個普遍規(guī)則來推廣啼县。

想獲取資料請回復【Java資料】獲得免費資料哦~

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末材原,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子季眷,更是在濱河造成了極大的恐慌华糖,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘟裸,死亡現(xiàn)場離奇詭異客叉,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門兼搏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卵慰,“玉大人,你說我怎么就攤上這事佛呻∩雅螅” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵吓著,是天一觀的道長鲤嫡。 經常有香客問我,道長绑莺,這世上最難降的妖魔是什么暖眼? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮纺裁,結果婚禮上诫肠,老公的妹妹穿的比我還像新娘。我一直安慰自己欺缘,他們只是感情好栋豫,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谚殊,像睡著了一般丧鸯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嫩絮,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天骡送,我揣著相機與錄音,去河邊找鬼絮记。 笑死忧陪,一個胖子當著我的面吹牛戳气,可吹牛的內容都是我干的粹断。 我是一名探鬼主播癌压,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼撰洗!你這毒婦竟也來了篮愉?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤差导,失蹤者是張志新(化名)和其女友劉穎试躏,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體设褐,經...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡颠蕴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年泣刹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片犀被。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡椅您,死狀恐怖,靈堂內的尸體忽然破棺而出寡键,到底是詐尸還是另有隱情掀泳,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布西轩,位于F島的核電站员舵,受9級特大地震影響,放射性物質發(fā)生泄漏藕畔。R本人自食惡果不足惜马僻,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望劫流。 院中可真熱鬧,春花似錦丛忆、人聲如沸祠汇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽可很。三九已至,卻和暖如春凰浮,著一層夾襖步出監(jiān)牢的瞬間我抠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工袜茧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留菜拓,地道東北人。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓笛厦,卻偏偏與公主長得像纳鼎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子裳凸,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內容