- 引入垃圾回收
- 哪些內(nèi)存需要回收?
- 引用計(jì)數(shù)法
- 可達(dá)性分析
- 如何回收
- Marking 標(biāo)記
- Normal Deletion 清除
- Deletion with Compacting 壓縮
- 為什么需要分代收集绅你?
- JVM的分代
- 新生代
- 老年代
- 永久代
- 分代垃圾收集過(guò)程詳述
引入垃圾回收
程序計(jì)數(shù)器近迁、 虛擬機(jī)棧嚎卫、 本地方法棧3個(gè)區(qū)域隨線程而生狰域,隨線程而滅永部;棧中的棧幀隨著方法的進(jìn)入和退出而有條不紊地執(zhí)行著出棧和入棧操作字管。 每一個(gè)棧幀中分配多少內(nèi)存基本上是在類結(jié)構(gòu)確定下來(lái)時(shí)就已知的(盡管在運(yùn)行期會(huì)由JIT編譯器
進(jìn)行一些優(yōu)化啰挪,但在本章基于概念模型的討論中信不,大體上可以認(rèn)為是編譯期可知的),因此這幾個(gè)區(qū)域的內(nèi)存分配和回收都具備確定性亡呵,在這幾個(gè)區(qū)域內(nèi)就不需要過(guò)多考慮回收的問(wèn)題抽活,因?yàn)榉椒ńY(jié)束或者線程結(jié)束時(shí),內(nèi)存自然就跟隨著回收了锰什。 而Java堆和方法區(qū)則不一樣下硕,一個(gè)接口中的多個(gè)實(shí)現(xiàn)類需要的內(nèi)存可能不一樣,一個(gè)方法中的多個(gè)分支需要的內(nèi)存也可能不一樣汁胆,我們只有在程序處于運(yùn)行期間時(shí)才能知道會(huì)創(chuàng)建哪些對(duì)象梭姓,這部分內(nèi)存的分配和回收都是動(dòng)態(tài)的,垃圾收集器所關(guān)注的是這部分內(nèi)存-----《深入理解Java虛擬機(jī)》
自動(dòng)垃圾回收機(jī)制就是尋找Java堆中的對(duì)象嫩码,并對(duì)對(duì)象進(jìn)行分類判別誉尖,尋找出正在使用的對(duì)象和已經(jīng)不會(huì)使用的對(duì)象,然后把那些不會(huì)使用的對(duì)象從堆上清除铸题。
自動(dòng)垃圾回收機(jī)制就是要解決三個(gè)問(wèn)題:
- 哪些內(nèi)存需要回收铡恕?
- 什么時(shí)候回收?
- 如何回收丢间?
哪些內(nèi)存需要回收探熔?
引用計(jì)數(shù)法
對(duì)于第一個(gè)問(wèn)題,也就是判斷是否還需要使用烘挫,最簡(jiǎn)單的方法就是通過(guò)目前是否有引用指向這個(gè)對(duì)象诀艰,如果沒(méi)有就說(shuō)明這個(gè)對(duì)象不會(huì)再被使用了,如果有就說(shuō)明這個(gè)對(duì)象可能還會(huì)繼續(xù)被使用饮六,這種通過(guò)引用是否存在的方法就叫做引用計(jì)數(shù)法涡驮,但這個(gè)方法存在一個(gè)問(wèn)題就是無(wú)法解決對(duì)象循環(huán)引用的問(wèn)題,因此又出現(xiàn)了可達(dá)性分析的方法來(lái)判斷對(duì)象是否可以被會(huì)回收喜滨。
可達(dá)性分析
這個(gè)算法的基本思路就是通過(guò)一系列的稱為“GC Roots”的對(duì)象作為起始點(diǎn)捉捅,從這些節(jié)點(diǎn)開始向下搜索,搜索所走過(guò)的路徑稱為引用鏈(Reference Chain)虽风,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連(用圖論的話來(lái)說(shuō)棒口,就是從GC Roots到這個(gè)對(duì)象不可達(dá))時(shí),則證明此對(duì)象是不可用的辜膝。
在Java語(yǔ)言中无牵,可作為GC Roots的對(duì)象包括下面幾種:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象。
- 方法區(qū)中類靜態(tài)屬性引用的對(duì)象厂抖。
- 方法區(qū)中常量引用的對(duì)象茎毁。
- 本地方法棧中JNI(即一般說(shuō)的Native方法)引用的對(duì)象。
如何回收
垃圾收集器通常會(huì)幫我們?cè)诤笈_(tái)自動(dòng)進(jìn)行垃圾回收。關(guān)于具體的回收過(guò)程只要有以下這些步驟
- Step 1: Marking 標(biāo)記
第一步就是標(biāo)記七蜘,也就是垃圾收集器會(huì)找出那些需要回收的對(duì)象所在的內(nèi)存和不需要回收的對(duì)象所在的內(nèi)存谭溉,并把它們標(biāo)記出來(lái),簡(jiǎn)單的說(shuō)橡卤,也就是先找出垃圾在哪
所有堆中的對(duì)象都會(huì)被掃描一遍扮念,以此來(lái)確定回收的對(duì)象,所以這通常會(huì)是一個(gè)相對(duì)比較耗時(shí)的過(guò)程
- Step 2: Normal Deletion
垃圾收集器會(huì)清除掉上一步標(biāo)記出來(lái)的那些需要回收的對(duì)象區(qū)域
存在的問(wèn)題就是碎片問(wèn)題:
標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片碧库,空間碎片太多可能會(huì)導(dǎo)致以后在程
序運(yùn)行過(guò)程中需要分配較大對(duì)象時(shí)柜与,無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作。
- Step 2a: Deletion with Compacting 壓縮
由于簡(jiǎn)單的清除可能會(huì)存在碎片的問(wèn)題嵌灰,所以又出現(xiàn)了壓縮清除的方法弄匕,也就是先清除需要回收的對(duì)象,然后再對(duì)內(nèi)存進(jìn)行壓縮操作沽瞭,將內(nèi)存分成可用和不可用兩大部分
為什么需要分代收集迁匠?
就像前文所述,標(biāo)記對(duì)象和壓縮內(nèi)存的過(guò)程在JVM中是不高效的秕脓,分配的對(duì)象越多,垃圾收集的時(shí)間就越長(zhǎng)儒搭。但是吠架,經(jīng)過(guò)一些經(jīng)驗(yàn)型性的統(tǒng)計(jì)分析表明,一個(gè)程序中大部分對(duì)象都是短命的搂鲫!
下圖就是一個(gè)類似的統(tǒng)計(jì)數(shù)據(jù)傍药,縱坐標(biāo)表示分配對(duì)象所占用的內(nèi)存大小,橫坐標(biāo)表示自分配對(duì)象過(guò)去的時(shí)間
從圖中我們看到魂仍,大部分對(duì)象沒(méi)活多久就死了拐辽,存活較久的只是少類對(duì)象
JVM的分代
為了增大垃圾收集的效率,所以JVM將堆進(jìn)行分代擦酌,分為不同的部分俱诸,一般有三部分,新生代赊舶,老年代和永久代
新生代
所有新new出來(lái)的對(duì)象都會(huì)最先出現(xiàn)在新生代中睁搭,當(dāng)新生代這部分內(nèi)存滿了之后,就會(huì)發(fā)起一次垃圾收集事件笼平,這種發(fā)生在新生代的垃圾收集稱為Minor collections园骆。這種收集通常比較快,因?yàn)樾律拇蟛糠謱?duì)象都是需要回收的寓调,那些暫時(shí)無(wú)法回收的就會(huì)被移動(dòng)到老年代锌唾。
Stop the World事件-所有minor garbage collections都是Stop the World事件,也就是意味著所有的應(yīng)用線程都需要停止夺英,直到垃圾回收的操作全部完成晌涕。類似于
“你媽媽在給你打掃房間的時(shí)候滋捶,肯定也會(huì)讓你老老實(shí)實(shí)地在椅子上或者房間外待著,如果她一邊打掃渐排,你一邊亂扔紙屑异雁,這房間還能打掃完锁蠕?”
老年代
老年代用來(lái)存儲(chǔ)那些存活時(shí)間較長(zhǎng)的對(duì)象。一般來(lái)說(shuō),我們會(huì)給新生代的對(duì)象限定一個(gè)存活的時(shí)間蹋半,當(dāng)達(dá)到這個(gè)時(shí)間還沒(méi)有被收集的時(shí)候就會(huì)被移動(dòng)到老年代中。老年代區(qū)域的垃圾收集叫做major garbage collection
Major garbage collection也是一個(gè)Stop the World事件弃甥。通常Major garbage collection都相對(duì)比較慢惶室,因?yàn)槔夏甏氖占藢?duì)所有對(duì)象的收集,也就是同時(shí)需要收集新生代和老年代的對(duì)象帘靡。
永久代
The Permanent generation contains metadata required by the JVM to describe the classes and methods used in the application. The permanent generation is populated by the JVM at runtime based on classes in use by the application. In addition, Java SE library classes and methods may be stored here.
Classes may get collected (unloaded) if the JVM finds they are no longer needed and space may be needed for other classes. The permanent generation is included in a full garbage collection.
分代垃圾收集過(guò)程詳述
我們已經(jīng)知道垃圾回收所需要的方法和堆內(nèi)存的分代知给,那么接下來(lái)我們就來(lái)具體看一下垃圾回收的具體過(guò)程
- 第一步 所有new出來(lái)的對(duì)象都會(huì)最先分配到新生代區(qū)域中,兩個(gè)survivor區(qū)域初始化是為空的
- 第二步描姚,當(dāng)eden區(qū)域滿了之后涩赢,就引發(fā)一次 minor garbage collection
- 第三步,當(dāng)在minor garbage collection轩勘,存活下來(lái)的對(duì)象就會(huì)被移動(dòng)到S0survivor區(qū)域
- 第四步筒扒,然后當(dāng)eden區(qū)域又填滿的時(shí)候,又會(huì)發(fā)生下一次的垃圾回收绊寻,存活的對(duì)象會(huì)被移動(dòng)到survivor區(qū)域而未存活對(duì)象會(huì)被直接刪除花墩。但是,不同的是澄步,在這次的垃圾回收中冰蘑,存活對(duì)象和之前的survivor中的對(duì)象都會(huì)被移動(dòng)到s1中。一旦所有對(duì)象都被移動(dòng)到s1中村缸,那么s2中的對(duì)象就會(huì)被清除祠肥,仔細(xì)觀察圖中的對(duì)象,數(shù)字表示經(jīng)歷的垃圾收集的次數(shù)梯皿。目前我們已經(jīng)有不同的年齡對(duì)象了搪柑。
- 第五步,下一次垃圾回收的時(shí)候索烹,又會(huì)重復(fù)上次的步驟工碾,清除需要回收的對(duì)象,并且又切換一次survivor區(qū)域百姓,所有存活的對(duì)象都被移動(dòng)至s0渊额。eden和s1區(qū)域被清除。
- 第六步,重復(fù)以上步驟旬迹,并記錄對(duì)象的年齡火惊,當(dāng)有對(duì)象的年齡到達(dá)一定的閾值的時(shí)候,就將新生代中的對(duì)象移動(dòng)到老年代中奔垦。在本例中屹耐,這個(gè)閾值為8.
- 第七步,接下來(lái)垃圾收集器就會(huì)重復(fù)以上步驟椿猎,不斷的進(jìn)行對(duì)象的清除和年代的移動(dòng)
- 最后惶岭,我們觀察上述過(guò)程可以發(fā)現(xiàn),大部分的垃圾收集過(guò)程都是在新生代進(jìn)行的犯眠,直到老年代中的內(nèi)存不夠用了才會(huì)發(fā)起一次 major GC按灶,會(huì)進(jìn)行標(biāo)記和整理壓縮。