阿里面試官都愛問的內(nèi)存管理和GC算法及回收策略

JVM內(nèi)存組成結(jié)構(gòu)

JVM棧由堆瘩缆、棧疤估、本地方法棧思灌、方法區(qū)等部分組成骑丸,結(jié)構(gòu)圖如下所示:

JVM內(nèi)存回收

Sun的JVMGenerationalCollecting(垃圾回收)原理是這樣的:把對象分為年青代(Young)舌仍、年老代(Tenured)妒貌、持久代(Perm),對不同生命周期的對象使用不同的算法铸豁。(基于對對象生命周期分析)

1.Young(年輕代)

年輕代分三個區(qū)灌曙。一個Eden區(qū),兩個Survivor區(qū)节芥。大部分對象在Eden區(qū)中生成在刺。當(dāng)Eden區(qū)滿時,還存活的對象將被復(fù)制到Survivor區(qū)(兩個中的一個)头镊,當(dāng)這個Survivor區(qū)滿時蚣驼,此區(qū)的存活對象將被復(fù)制到另外一個Survivor區(qū),當(dāng)這個Survivor去也滿了的時候相艇,從第一個Survivor區(qū)復(fù)制過來的并且此時還存活的對象颖杏,將被復(fù)制年老區(qū)(Tenured。需要注意坛芽,Survivor的兩個區(qū)是對稱的留储,沒先后關(guān)系,所以同一個區(qū)中可能同時存在從Eden復(fù)制過來對象咙轩,和從前一個Survivor復(fù)制過來的對象获讳,而復(fù)制到年老區(qū)的只有從第一個Survivor去過來的對象。而且臭墨,Survivor區(qū)總有一個是空的赔嚎。

2.Tenured(年老代)

年老代存放從年輕代存活的對象膘盖。一般來說年老代存放的都是生命期較長的對象胧弛。

3.Perm(持久代)

用于存放靜態(tài)文件,如今Java類侠畔、方法等结缚。持久代對垃圾回收沒有顯著影響,但是有些應(yīng)用可能動態(tài)生成或者調(diào)用一些class软棺,例如Hibernate等红竭,在這種時候需要設(shè)置一個比較大的持久代空間來存放這些運行過程中新增的類。持久代大小通過-XX:MaxPermSize=進(jìn)行設(shè)置喘落。

舉個例子:當(dāng)在程序中生成對象時茵宪,正常對象會在年輕代中分配空間,如果是過大的對象也可能會直接在年老代生成(據(jù)觀測在運行某程序時候每次會生成一個十兆的空間用收發(fā)消息瘦棋,這部分內(nèi)存就會直接在年老代分配)稀火。年輕代在空間被分配完的時候就會發(fā)起內(nèi)存回收,大部分內(nèi)存會被回收赌朋,一部分幸存的內(nèi)存會被拷貝至Survivor的from區(qū)凰狞,經(jīng)過多次回收以后如果from區(qū)內(nèi)存也分配完畢篇裁,就會也發(fā)生內(nèi)存回收然后將剩余的對象拷貝至to區(qū)。等到to區(qū)也滿的時候赡若,就會再次發(fā)生內(nèi)存回收然后把幸存的對象拷貝至年老區(qū)达布。

通常我們說的JVM內(nèi)存回收總是在指堆內(nèi)存回收,確實只有堆中的內(nèi)容是動態(tài)申請分配的逾冬,所以以上對象的年輕代和年老代都是指的JVM的Heap空間黍聂,而持久代則是之前提到的MethodArea,不屬于Heap身腻。

關(guān)于JVM內(nèi)存管理的一些建議

  1. 手動將生成的無用對象分冈,中間對象置為null,加快內(nèi)存回收霸株。

  2. 對象池技術(shù)如果生成的對象是可重用的對象雕沉,只是其中的屬性不同時,可以考慮采用對象池來較少對象的生成去件。如果有空閑的對象就從對象池中取出使用坡椒,沒有再生成新的對象,大大提高了對象的復(fù)用率尤溜。

  3. JVM調(diào)優(yōu)通過配置JVM的參數(shù)來提高垃圾回收的速度倔叼,如果在沒有出現(xiàn)內(nèi)存泄露且上面兩種辦法都不能保證JVM內(nèi)存回收時,可以考慮采用JVM調(diào)優(yōu)的方式來解決宫莱,不過一定要經(jīng)過實體機的長期測試丈攒,因為不同的參數(shù)可能引起不同的效果。如-Xnoclassgc參數(shù)等授霸。

垃圾對象的判定

Java堆中存放著幾乎所有的對象實例巡验,垃圾收集器對堆中的對象進(jìn)行回收前,要先確定這些對象是否還有用碘耳,判定對象是否為垃圾對象有如下算法:

引用計數(shù)算法

給對象添加一個引用計數(shù)器显设,每當(dāng)有一個地方引用它時,計數(shù)器值就加1辛辨,當(dāng)引用失效時捕捂,計數(shù)器值就減1,任何時刻計數(shù)器都為0的對象就是不可能再被使用的斗搞。

引用計數(shù)算法的實現(xiàn)簡單指攒,判定效率也很高,在大部分情況下它都是一個不錯的選擇僻焚,當(dāng)Java語言并沒有選擇這種算法來進(jìn)行垃圾回收允悦,主要原因是它很難解決對象之間的相互循環(huán)引用問題。

根搜索算法

Java和C#中都是采用根搜索算法來判定對象是否存活的溅呢。這種算法的基本思路是通過一系列名為“GC Roots”的對象作為起始點澡屡,從這些節(jié)點開始向下搜索猿挚,搜索所走過的路徑稱為引用鏈,當(dāng)一個對象到GC Roots沒有任何引用鏈相連時驶鹉,就證明此對象是不可用的绩蜻。在Java語言里,可作為GC Roots的兌現(xiàn)包括下面幾種:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象室埋。
  • 方法區(qū)中的類靜態(tài)屬性引用的對象办绝。
  • 方法區(qū)中的常量引用的對象。
  • 本地方法棧中JNI(Native方法)的引用對象姚淆。

實際上孕蝉,在根搜索算法中,要真正宣告一個對象死亡腌逢,至少要經(jīng)歷兩次標(biāo)記過程:如果對象在進(jìn)行根搜索后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈降淮,那它會被第一次標(biāo)記并且進(jìn)行一次篩選,篩選的條件是此對象是否有必要執(zhí)行finalize()方法搏讶。當(dāng)對象沒有覆蓋finalize()方法佳鳖,或finalize()方法已經(jīng)被虛擬機調(diào)用過,虛擬機將這兩種情況都視為沒有必要執(zhí)行媒惕。如果該對象被判定為有必要執(zhí)行finalize()方法系吩,那么這個對象將會被放置在一個名為F-Queue隊列中,并在稍后由一條由虛擬機自動建立的妒蔚、低優(yōu)先級的Finalizer線程去執(zhí)行finalize()方法穿挨。finalize()方法是對象逃脫死亡命運的最后一次機會(因為一個對象的finalize()方法最多只會被系統(tǒng)自動調(diào)用一次),稍后GC將對F-Queue中的對象進(jìn)行第二次小規(guī)模的標(biāo)記肴盏,如果要在finalize()方法中成功拯救自己科盛,只要在finalize()方法中讓該對象重新引用鏈上的任何一個對象建立關(guān)聯(lián)即可。而如果對象這時還沒有關(guān)聯(lián)到任何鏈上的引用叁鉴,那它就會被回收掉土涝。

垃圾收集算法

判定除了垃圾對象之后,便可以進(jìn)行垃圾回收了幌墓。下面介紹一些垃圾收集算法,由于垃圾收集算法的實現(xiàn)涉及大量的程序細(xì)節(jié)冀泻,因此這里主要是闡明各算法的實現(xiàn)思想常侣,而不去細(xì)論算法的具體實現(xiàn)。

標(biāo)記—清除算法

標(biāo)記—清除算法是最基礎(chǔ)的收集算法弹渔,它分為“標(biāo)記”和“清除”兩個階段:首先標(biāo)記出所需回收的對象胳施,在標(biāo)記完成后統(tǒng)一回收掉所有被標(biāo)記的對象,它的標(biāo)記過程其實就是前面的根搜索算法中判定垃圾對象的標(biāo)記過程肢专。標(biāo)記—清除算法的執(zhí)行情況如下圖所示:

該算法有如下缺點:

  • 標(biāo)記和清除過程的效率都不高舞肆。
  • 標(biāo)記清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片焦辅,空間碎片太多可能會導(dǎo)致,當(dāng)程序在以后的運行過程中需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不觸發(fā)另一次垃圾收集動作椿胯。
復(fù)制算法

復(fù)制算法比較適合于新生代筷登,復(fù)制算法是針對標(biāo)記—清除算法的缺點,在其基礎(chǔ)上進(jìn)行改進(jìn)而得到的,它講課用內(nèi)存按容量分為大小相等的兩塊,每次只使用其中的一塊根资,當(dāng)這一塊的內(nèi)存用完了柄瑰,就將還存活著的對象復(fù)制到另外一塊內(nèi)存上面,然后再把已使用過的內(nèi)存空間一次清理掉诡蜓。復(fù)制算法有如下優(yōu)點:

  • 每次只對一塊內(nèi)存進(jìn)行回收,運行高效。
  • 只需移動棧頂指針班巩,按順序分配內(nèi)存即可,實現(xiàn)簡單嘶炭。
  • 內(nèi)存回收時不用考慮內(nèi)存碎片的出現(xiàn)趣竣。

它的缺點是:可一次性分配的最大內(nèi)存縮小了一半。
復(fù)制算法的執(zhí)行情況如下圖所示:

但一般不用按1:1劃分內(nèi)存空間旱物,可以分成一個大的eden和兩塊小的survivor遥缕。

標(biāo)記—整理算法

老年代中,對象存活率比較高宵呛,如果執(zhí)行較多的復(fù)制操作单匣,效率將會變低,所以老年代一般會選用其他算法宝穗,如標(biāo)記—整理算法户秤。該算法標(biāo)記的過程與標(biāo)記—清除算法中的標(biāo)記過程一樣,但對標(biāo)記后出的垃圾對象的處理情況有所不同逮矛,它不是直接對可回收對象進(jìn)行清理鸡号,而是讓所有的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存须鼎。標(biāo)記—整理算法的回收情況如下所示:

分代收集

當(dāng)前商業(yè)虛擬機的垃圾收集都采用分代收集來管理內(nèi)存鲸伴,它根據(jù)對象的存活周期的不同將內(nèi)存劃分為幾塊,一般是把Java堆分為新生代和老年代晋控。在新生代中汞窗,每次垃圾收集時都會發(fā)現(xiàn)有大量對象死去,只有少量存活赡译,因此可選用復(fù)制算法來完成收集仲吏,而老年代中因為對象存活率高、沒有額外空間對它進(jìn)行分配擔(dān)保,就必須使用標(biāo)記—清除算法或標(biāo)記—整理算法來進(jìn)行回收裹唆。

每個對象都有一個年齡(Age)計數(shù)器誓斥,如果對象在Eden出聲并講過一次Minor GC還存活,將被移動到Survivor區(qū)并將Age設(shè)置為1许帐,之后每在Survivor區(qū)中熬過一次Minor GC劳坑,Age就加1,當(dāng)增加到一定程度(默認(rèn)為15)舞吭,就可以放到老年代中泡垃。

垃圾收集器

垃圾收集器是內(nèi)存回收算法的具體實現(xiàn),Java虛擬機規(guī)范中對垃圾收集器應(yīng)該如何實現(xiàn)并沒有任何規(guī)定羡鸥,因此不同廠商蔑穴、不同版本的虛擬機所提供的垃圾收集器都可能會有很大的差別。Sun HotSpot虛擬機1.6版包含了如下收集器:Serial惧浴、ParNew存和、Parallel Scavenge、CMS衷旅、Serial Old捐腿、Parallel Old。這些收集器以不同的組合形式配合工作來完成不同分代區(qū)的垃圾收集工作柿顶。

垃圾回收分析

在用代碼分析之前茄袖,我們對內(nèi)存的分配策略明確以下三點:

  • 對象優(yōu)先在Eden分配。當(dāng)Eden沒有足夠空間分配時嘁锯,將發(fā)起一次Minor GC
  • 大對象(需要大量連續(xù)空間的java對象宪祥,如長的字符串和數(shù)組)直接進(jìn)入老年代。由于新生代使用復(fù)制算法回收內(nèi)存家乘,這樣可以避免在Eden和兩個Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制蝗羊。
  • 長期存活的對象將進(jìn)入老年代。

對垃圾回收策略說明以下兩點:

  • 新生代GC(Minor GC):發(fā)生在新生代的垃圾收集動作仁锯,因為Java對象大多都具有朝生夕滅的特性耀找,因此Minor GC非常頻繁,一般回收速度也比較快业崖。
  • 老年代GC(Major GC/Full GC):發(fā)生在老年代的GC野芒,出現(xiàn)了Major GC,經(jīng)常會伴隨至少一次Minor GC腻要。由于老年代中的對象生命周期比較長复罐,因此Major GC并不頻繁,一般都是等待老年代滿了后才進(jìn)行Full GC雄家,而且其速度一般會比Minor GC慢10倍以上。另外,如果分配了Direct Memory趟济,在老年代中進(jìn)行Full GC時乱投,會順便清理掉Direct Memory中的廢棄對象。

Dalvik虛擬機使用Mark-Sweep算法來進(jìn)行垃圾收集顷编。顧名思義戚炫,Mark-Sweep算法就是為Mark和Sweep兩個階段進(jìn)行垃圾回收。其中媳纬,Mark階段從根集(Root Set)開始双肤,遞歸地標(biāo)記出當(dāng)前所有被引用的對象,而Sweep階段負(fù)責(zé)回收那些沒有被引用的對象钮惠。在分析Dalvik虛擬機使用的Mark-Sweep算法之前茅糜,我們先來了解一下什么情況下會觸發(fā)GC。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末素挽,一起剝皮案震驚了整個濱河市蔑赘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌预明,老刑警劉巖缩赛,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異撰糠,居然都是意外死亡酥馍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進(jìn)店門阅酪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旨袒,“玉大人,你說我怎么就攤上這事遮斥÷褪В” “怎么了?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵术吗,是天一觀的道長尉辑。 經(jīng)常有香客問我,道長较屿,這世上最難降的妖魔是什么隧魄? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮隘蝎,結(jié)果婚禮上购啄,老公的妹妹穿的比我還像新娘。我一直安慰自己嘱么,他們只是感情好狮含,可當(dāng)我...
    茶點故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般几迄。 火紅的嫁衣襯著肌膚如雪蔚龙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天映胁,我揣著相機與錄音木羹,去河邊找鬼。 笑死解孙,一個胖子當(dāng)著我的面吹牛坑填,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弛姜,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼脐瑰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了娱据?” 一聲冷哼從身側(cè)響起蚪黑,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎中剩,沒想到半個月后忌穿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡结啼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年掠剑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郊愧。...
    茶點故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡朴译,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出属铁,到底是詐尸還是另有隱情眠寿,我是刑警寧澤,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布焦蘑,位于F島的核電站盯拱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏例嘱。R本人自食惡果不足惜狡逢,卻給世界環(huán)境...
    茶點故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拼卵。 院中可真熱鬧奢浑,春花似錦、人聲如沸腋腮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至详羡,卻和暖如春仍律,著一層夾襖步出監(jiān)牢的瞬間嘿悬,已是汗流浹背实柠。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留善涨,地道東北人窒盐。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像钢拧,于是被迫代替她去往敵國和親蟹漓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,937評論 2 361

推薦閱讀更多精彩內(nèi)容