Java 垃圾收集(GC)淺談

Java 垃圾收集(GC)淺談

為什么需要垃圾回收诱告?

哪些內(nèi)存需要回收?

什么時(shí)候回收吠架?

如何回收芙贫?

為什么需要垃圾回收?

? 當(dāng)需要排查各種內(nèi)存溢出傍药、內(nèi)存泄露問題時(shí)磺平,當(dāng)垃圾收集稱為系統(tǒng)達(dá)到更高并發(fā)量的瓶頸時(shí),我們就需要對這些“自動(dòng)化”的技術(shù)實(shí)施必要的監(jiān)控和調(diào)節(jié)拐辽。在構(gòu)建大型程序時(shí)褪秀,GC直接影響著內(nèi)存優(yōu)化和運(yùn)行速度。

Java 內(nèi)存區(qū)域

了解GC機(jī)制之前薛训,需要先搞清楚Java程序在執(zhí)行的時(shí)候媒吗,內(nèi)存究竟是如何劃分的。


gc_memo.jpg

私有內(nèi)存區(qū)的區(qū)域名和相應(yīng)的特性如下表所示:

區(qū)域名稱 特性
程序計(jì)數(shù)器 指示當(dāng)前程序執(zhí)行到了哪一行乙埃,執(zhí)行Java方法時(shí)記錄正在執(zhí)行的虛擬機(jī)字節(jié)地址闸英;執(zhí)行本地方法時(shí)锯岖,計(jì)數(shù)器值為undefined
虛擬機(jī)棧 用于執(zhí)行Java方法。棧幀存儲(chǔ)局部變量表甫何、操作數(shù)棧出吹、動(dòng)態(tài)鏈接、方法返回一些額外的附加信息辙喂。程序執(zhí)行時(shí)棧幀入棧捶牢;執(zhí)行完成后棧幀出棧
本地方法棧 用于執(zhí)行本地方法,其他和虛擬機(jī)棧類似

虛擬機(jī)棧中的局部變量表

里面存放了三個(gè)信息:

  • 各種基本數(shù)據(jù)類型(boolean巍耗、byte秋麸、char、short炬太、int灸蟆、float、long亲族、double)
  • 對象引用(reference)
  • returnAddress地址

這個(gè)returnAddress和程序計(jì)數(shù)器有什么區(qū)別炒考?前者是指示JVM的指令執(zhí)行到了哪一行,后者是指你的代碼執(zhí)行到哪一行霎迫。

哪些內(nèi)存需要回收斋枢?

私有內(nèi)存區(qū)伴隨著線程的產(chǎn)生而產(chǎn)生,一旦線程中止知给,私有內(nèi)存區(qū)也會(huì)自動(dòng)消除杏慰,因此我們在本文中討論的內(nèi)存回收主要是針對共享內(nèi)存區(qū)。

共享內(nèi)存區(qū):

區(qū)域名稱 特性
Java堆 Java虛擬機(jī)管理的內(nèi)存中最大的一塊炼鞠,所有線程共享缘滥,幾乎所有的對象實(shí)例數(shù)組都在這類分配內(nèi)存。GC主要就是在Java堆中進(jìn)行的谒主。
方法區(qū) 用于存儲(chǔ)已被虛擬機(jī)加載的類信息朝扼、常量、靜態(tài)變量霎肯、及時(shí)編譯器編譯后的代碼等數(shù)據(jù)擎颖。但是已被最新的JVM取消了。現(xiàn)在观游,被加載的類作為元數(shù)據(jù)加載到底層操作系統(tǒng)的本地內(nèi)存區(qū)搂捧。

Java 堆

堆內(nèi)存是由存活和死亡的對象組成的。存活的對象是應(yīng)用可以訪問的懂缕,不會(huì)被垃圾回收允跑。死亡的對象是應(yīng)用不可訪問尚且還沒有被垃圾收集器回收掉的對象。一直到垃圾收集器把這些對象回收掉之前,他們會(huì)一直占據(jù)堆內(nèi)存空間聋丝。堆是應(yīng)用程序在運(yùn)行期請求操作系統(tǒng)分配給自己的向搞地質(zhì)擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)索烹,是不連續(xù)的內(nèi)存區(qū)域。用一句話總結(jié)堆的作用:程序運(yùn)行時(shí)動(dòng)態(tài)申請某個(gè)大小的內(nèi)存空間弱睦。

gc_heap_generation.jpg

新生代GC(Minor GC):指發(fā)生在新生代的垃圾收集動(dòng)作百姓,因?yàn)镴ava對象大都具備朝生夕滅的特性,所以Minor GC 非常頻繁况木,一般回收速度也比較快垒拢。

老年代GC(Major GC/Full GC):指發(fā)生在老年代的GC,出現(xiàn)了Major GC火惊,經(jīng)常會(huì)伴隨至少一次的Minor GC (但非絕對求类,在Parallel Scavenge收集器的收集策略里就有直接進(jìn)行Major GC的策略選擇過程)。Major GC的速度一般會(huì)比Minor GC慢10倍以上

新生代:剛剛新建的對象在Eden中矗晃,經(jīng)歷一次Minor GC, Eden中的存貨對象就被移動(dòng)到第一塊survivor space S0仑嗅,Eden被清空宴倍;等Eden區(qū)再滿了张症,就再觸發(fā)一次Minor GC, Eden和S0中的存活對象會(huì)被復(fù)制送入第二塊survivor space S1。S0和Eden被清空鸵贬,然后下一輪S0與S1交換角色俗他,如此循環(huán)往復(fù)。如果對象的復(fù)制次數(shù)達(dá)到16此阔逼,改對象就被送到老年代中兆衅。

至于為什么興盛帶要分出兩個(gè)survivor區(qū),參考博客為什么新生代內(nèi)存需要有兩個(gè)Sruvivor區(qū)

老年代:如果某個(gè)對象經(jīng)歷了幾次垃圾回收之后還存活嗜浮,就會(huì)被存放到老年代中羡亩。老年代的空間一般比新生代大。

對象創(chuàng)建后的內(nèi)存分配

創(chuàng)建一個(gè)對象后危融,他會(huì)被放在堆內(nèi)存的哪個(gè)部分呢畏铆?

gc_object_create.jpg

什么時(shí)候回收?

Java并沒有給我們提供明確的代碼來標(biāo)注一塊內(nèi)存并將其回收吉殃〈蔷樱或許你會(huì)說,我們可以將相關(guān)對象設(shè)為null或者用System.gc()蛋勺。然而瓦灶,后者將會(huì)嚴(yán)重影響代碼的性能,因?yàn)?strong>每一次顯示調(diào)用system.gc()都會(huì)停止所有響應(yīng)抱完,去檢查內(nèi)存中是否有可回收的對象贼陶,這回對程序的正常運(yùn)行造成極大威脅。另外,調(diào)用該方法并不能保障JVM立即進(jìn)行垃圾回收每界,僅僅是通知JVM要進(jìn)行垃圾回收了捅僵,具體回收與否完全由JVM決定。

生存還是死亡

可達(dá)性算法

這個(gè)算法的基本思路是通過一系列的稱為“GC Roots”的對象作為起始點(diǎn)眨层,從這些節(jié)點(diǎn)開始向下搜索庙楚,搜索所走過的路徑稱為引用鏈(Reference Chain),當(dāng)一個(gè)對象到GC Roots沒有任何引用鏈相連時(shí)趴樱,則證明此對象是不可用的馒闷。

gc_reachability.png

在Java語言中,可作為GC Roots的對象包括下面幾種:

  • 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象
  • 方法區(qū)中靜態(tài)屬性引用的對象
  • 方法區(qū)中常量引用的對象
  • 本地方法棧中JNI(即一般說的Native方法)引用的對象

如何回收叁征?

標(biāo)記-清除(Mark-Sweep)算法

分為兩個(gè)階段:首先標(biāo)記出所有需要回收的對象纳账,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。
缺點(diǎn):效率問題捺疼,標(biāo)記和清除兩個(gè)過程的效率都不高疏虫;空間問題,會(huì)產(chǎn)生很多碎片啤呼。

gc_mark_sweep.jpg

復(fù)制算法

將可用內(nèi)存按容量劃分為大小相等的兩塊卧秘,每次只用其中一塊。當(dāng)這一塊用完了官扣,就將還存活的對象復(fù)制到另外一塊上面翅敌,然后把原始空間全部回收。高效惕蹄、簡單蚯涮。
缺點(diǎn):將內(nèi)存縮小為原來的一半。


gc_copying.jpg

標(biāo)記-整理(Mark-Compat)算法

gc_mark_compact.jpg

標(biāo)記過程與標(biāo)記-清除算法過程一樣卖陵,但后面不是簡單的清除遭顶,而是讓所有存活的對象都向一端移動(dòng),然后直接清除掉端邊界以外的內(nèi)存泪蔫。

分代收集(Generational Collection)算法

gc_generation.jpg
  • 新生代中棒旗,每次垃圾收集時(shí)都有大批對象死去,只有少量存活鸥滨,就選用復(fù)制算法嗦哆,只需要付出少量存活對象的復(fù)制成本就可以完成收集;
  • 老年代中婿滓,其存活率較高老速、沒有額外空間對它進(jìn)行分配擔(dān)保,就應(yīng)該使用“標(biāo)記-整理”“標(biāo)記-清除”算法進(jìn)行回收凸主。

一些收集器

Serial收集器

單線程收集器橘券,表示在它進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程,直到它收集結(jié)束旁舰。"Stop The World".

ParNew收集器

實(shí)際就是Serial收集器的多線程版本锋华。

  • 并發(fā)(Parallel):指多條垃圾收集線程并行工作,但此時(shí)用戶線程仍然處于等待狀態(tài)箭窜;
  • 并行(Concurrent):指用戶線程與垃圾收集線程同時(shí)執(zhí)行毯焕,用戶程序在繼續(xù)運(yùn)行,而垃圾收集程序運(yùn)行于另一個(gè)CPU上磺樱。

Parallel Scavenge收集器

該收集器比較關(guān)注吞吐量(Throughout)(CPU用于用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值)纳猫,保證吞吐量在一個(gè)可控的范圍內(nèi)。

CMS(Concurrent Mark Sweep)收集器

CMS收集器是一種以獲得最短停頓時(shí)間為目標(biāo)的收集器竹捉。

G1(Garbage First)收集器

從JDK1.7 Update 14之后的HotSpot虛擬機(jī)正式提供了商用的G1收集器芜辕,與其他收集器相比,它具有如下優(yōu)點(diǎn):并行與并發(fā)块差;分代收集侵续;空間整合;可預(yù)測的停頓等憨闰。

新生代收集器使用的收集器:Serial状蜗、ParNew、Parallel Scavenge

老年代收集器使用的收集器:Serial Old起趾、Parallel Old诗舰、CMS

gc_sjq.jpg

Java性能優(yōu)化

大多數(shù)針對內(nèi)存的調(diào)優(yōu)警儒,都是針對特定情況的训裆。但是實(shí)際中,調(diào)優(yōu)很難與Java運(yùn)行動(dòng)態(tài)特性的實(shí)際情況和工作負(fù)載保持一致蜀铲。也就是說边琉,幾乎不可能通過單純的調(diào)優(yōu)來消除GC的目的。

寫程序的時(shí)候應(yīng)該注意的點(diǎn):

  1. 減少new對象记劝。每次new對象之后变姨,都要開辟新的內(nèi)存空間。這些對象不被引用之后厌丑,還要回收掉定欧。因此,如果最大限度地合理重用對象怒竿,或者使用基本數(shù)據(jù)類型替代對象砍鸠,都有助于節(jié)省內(nèi)存。
  2. 多使用局部變量耕驰,減少使用靜態(tài)變量爷辱。局部變量被創(chuàng)建在棧中,存取速度快。靜態(tài)變量則是存儲(chǔ)在堆內(nèi)存中饭弓。
  3. 避免使用finalize双饥,該方法會(huì)給GC增添很大的負(fù)擔(dān)
  4. 如果是單線程,盡量使用非多線程安全的弟断,因?yàn)榫€程安全來自于同步機(jī)制咏花,同步機(jī)制會(huì)降低性能。例如阀趴,單線程程序迟螺,能使用HashMap,就不要使用HashTabl舍咖。同理矩父,盡量減少使用synchronized。
  5. 用移位符號替代乘除號排霉。比如:a*8應(yīng)該寫作a<<3
  6. 對于經(jīng)常反復(fù)使用的對象使用緩存窍株。
  7. 盡量使用基本類型而不是包裝類型,盡量使用一維數(shù)組而不是二維數(shù)組
  8. 盡量使用final修飾符攻柠,final表示不可修改球订,訪問效率高
  9. 單線程下(或者是針對于局部變量),字符串盡量使用StringBuilder,比StringBuffer要快
  10. 盡量使用StringBuffer來連接字符串瑰钮。這里需要注意的是冒滩,StringBuffer的默認(rèn)緩存容量是16個(gè)字符,如果超過16浪谴,append犯法調(diào)用私有的expandCapacity()方法开睡,來保證足夠的緩存容量。因此苟耻,如果可以預(yù)設(shè)StringBuffer的容量篇恒,避免append再去擴(kuò)展容量。

參考資料:

1.《深入理解Java虛擬機(jī)-JVM高級特性與最佳實(shí)踐》

2.橙子wj的博客 (強(qiáng)推)

3.Java性能優(yōu)化之JVM GC(垃圾回收機(jī)制)

4.面試題:“你能不能談?wù)勑渍龋琷ava GC是在什么時(shí)候胁艰,對什么東西,做了什么事情智蝠?”

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腾么,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子杈湾,更是在濱河造成了極大的恐慌解虱,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毛秘,死亡現(xiàn)場離奇詭異饭寺,居然都是意外死亡阻课,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門艰匙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來限煞,“玉大人,你說我怎么就攤上這事员凝∈鹱ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵健霹,是天一觀的道長旺上。 經(jīng)常有香客問我,道長糖埋,這世上最難降的妖魔是什么宣吱? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮瞳别,結(jié)果婚禮上征候,老公的妹妹穿的比我還像新娘。我一直安慰自己祟敛,他們只是感情好疤坝,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著馆铁,像睡著了一般跑揉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上埠巨,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天历谍,我揣著相機(jī)與錄音,去河邊找鬼乖订。 笑死扮饶,一個(gè)胖子當(dāng)著我的面吹牛具练,可吹牛的內(nèi)容都是我干的乍构。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼扛点,長吁一口氣:“原來是場噩夢啊……” “哼哥遮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起陵究,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤眠饮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后铜邮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仪召,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寨蹋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扔茅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片已旧。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖召娜,靈堂內(nèi)的尸體忽然破棺而出运褪,到底是詐尸還是另有隱情,我是刑警寧澤玖瘸,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布秸讹,位于F島的核電站,受9級特大地震影響雅倒,放射性物質(zhì)發(fā)生泄漏璃诀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一蔑匣、第九天 我趴在偏房一處隱蔽的房頂上張望文虏。 院中可真熱鬧,春花似錦殖演、人聲如沸氧秘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丸相。三九已至,卻和暖如春彼棍,著一層夾襖步出監(jiān)牢的瞬間灭忠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工座硕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弛作,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓华匾,卻偏偏與公主長得像映琳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子蜘拉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

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