新聞中心
Java 之所以能夠如此流行,自動(dòng) 垃圾回收Garbage Collection(GC)功不可沒(méi),它也是 Java 最重要的幾個(gè)特性之一。在這篇文章中,我將說(shuō)明為什么垃圾回收如此重要。本文的主要內(nèi)容為:自動(dòng)的分代垃圾回收、JVM 劃分內(nèi)存的依據(jù),以及 JVM 垃圾回收的工作原理。

Java 內(nèi)存分配
Java 程序的內(nèi)存空間被劃分為以下四個(gè)區(qū)域:
- 堆區(qū)Heap:對(duì)象實(shí)例就是在這個(gè)區(qū)域分配的。不過(guò),當(dāng)我們聲明一個(gè)對(duì)象時(shí),堆中不會(huì)發(fā)生任何內(nèi)存分配,只是在棧中創(chuàng)建了一個(gè)對(duì)象的引用而已。
- 棧區(qū)Stack:方法、局部變量和類(lèi)的實(shí)例變量就是在這個(gè)區(qū)域分配的。
- 代碼區(qū)Code:這個(gè)區(qū)域存放了程序的字節(jié)碼。
- 靜態(tài)區(qū)Static:這個(gè)區(qū)域存放了程序的靜態(tài)數(shù)據(jù)和靜態(tài)方法。
什么是自動(dòng)垃圾回收?
自動(dòng)垃圾回收是這樣一個(gè)過(guò)程:首先,堆中的所有對(duì)象會(huì)被分類(lèi)為“被引用的”和“未被引用的”;接著,“未被引用的對(duì)象”就會(huì)被做上標(biāo)記,以待之后刪除。其中,“被引用的對(duì)象”是指程序中的某一部分仍在使用的對(duì)象,“未被引用的對(duì)象”是指目前沒(méi)有正在被使用的對(duì)象。
許多編程語(yǔ)言,例如 C 和 C++,都需要程序員手動(dòng)管理內(nèi)存的分配和釋放。在 Java 中,這一過(guò)程是通過(guò)垃圾回收機(jī)制來(lái)自動(dòng)完成的(盡管你也可以在代碼中調(diào)用 system.gc(); 來(lái)手動(dòng)觸發(fā)垃圾回收)。
垃圾回收的基本步驟如下:
1、標(biāo)記已使用和未使用的對(duì)象
在這一步驟中,已使用和未使用的對(duì)象會(huì)被分別做上標(biāo)記。這是一個(gè)及其耗時(shí)的過(guò)程,因?yàn)樾枰獟呙鑳?nèi)存中的所有對(duì)象,才能夠確定它們是否正在被使用。
標(biāo)記已使用和未使用的對(duì)象
2、掃描/刪除對(duì)象
有兩種不同的掃描和刪除算法:
簡(jiǎn)單刪除(標(biāo)記清除):它的過(guò)程很簡(jiǎn)單,我們只需要?jiǎng)h除未被引用的對(duì)象即可。但是,后續(xù)給新對(duì)象分配內(nèi)存就會(huì)變得很困難了,因?yàn)榭捎每臻g被分割成了一塊塊碎片。
標(biāo)記清除的過(guò)程
刪除壓縮(標(biāo)記整理):除了會(huì)刪除未被引用的對(duì)象,我們還會(huì)壓縮被引用的對(duì)象(未被刪除的對(duì)象)。這樣以來(lái),新對(duì)象的內(nèi)存分配就相對(duì)容易了,并且內(nèi)存分配的效率也有了提升。
標(biāo)記整理的過(guò)程
什么是分代垃圾回收,為什么需要它?
正如我們?cè)凇皰呙鑴h除”模型中所看到的,一旦對(duì)象不斷增長(zhǎng),我們就很難掃描所有未使用的對(duì)象以回收內(nèi)存。不過(guò),有一項(xiàng)實(shí)驗(yàn)性研究指出,在程序執(zhí)行期間創(chuàng)建的大多數(shù)對(duì)象,它們的存活時(shí)間都很短。
既然大多數(shù)對(duì)象的存活時(shí)間都很短,那么我們就可以利用這個(gè)事實(shí),從而提升垃圾回收的效率。該怎么做呢?首先,JVM 將內(nèi)存劃分為不同的“代”。接著,它將所有的對(duì)象都分類(lèi)到這些內(nèi)存“代”中,然后對(duì)這些“代”分別執(zhí)行垃圾回收。這就是“分代垃圾回收”。
堆內(nèi)存的“代”和分代垃圾回收過(guò)程
為了提升垃圾回收中的“標(biāo)記清除”的效率,JVM 將對(duì)內(nèi)存劃分成以下三個(gè)“代”:
- 新生代Young Generation
- 老年代Old Generation
- 永久代Permanent Generation
Hotspot 堆內(nèi)存結(jié)構(gòu)
下面我將介紹每個(gè)“代”及其主要特征。
新生代
所有創(chuàng)建不久的對(duì)象都存放在這里。新生代被進(jìn)一步分為以下兩個(gè)區(qū)域:
- 伊甸區(qū)Eden:所有新創(chuàng)建的對(duì)象都在此處分配內(nèi)存。
- 幸存者區(qū)Survivor,分為 S0 和 S1:經(jīng)歷過(guò)一次垃圾回收后,仍然存活的對(duì)象會(huì)被移動(dòng)到兩個(gè)幸存者區(qū)中的一個(gè)。
對(duì)象分配
在新生代發(fā)生的分代垃圾回收被稱(chēng)為 “次要回收Minor GC”(LCTT 譯注:也稱(chēng)為“新生代回收Young GC”)。Minor GC 過(guò)程中的每個(gè)階段都是“停止世界Stop The World”(STW)的,這會(huì)導(dǎo)致其他應(yīng)用程序暫停運(yùn)行,直到垃圾回收結(jié)束。這也是次要回收更快的原因。
一句話(huà)總結(jié):伊甸區(qū)存放了所有新創(chuàng)建的對(duì)象,當(dāng)它的可用空間被耗盡,第一次垃圾回收就會(huì)被觸發(fā)。
填充伊甸區(qū)
次要回收:在該垃圾回收過(guò)程中,所有存活和死亡的對(duì)象都會(huì)被做上標(biāo)記。其中,存活對(duì)象會(huì)被移動(dòng)到 S0 幸存者區(qū)。當(dāng)所有存活對(duì)象都被移動(dòng)到了 S0,未被引用的對(duì)象就會(huì)被刪除。
拷貝被引用的對(duì)象
S0 中的對(duì)象年齡為 1,因?yàn)樗鼈兺^(guò)了一次次要回收。此時(shí),伊甸區(qū)和 S1 都是空的。
每當(dāng)完成清理后,伊甸區(qū)就會(huì)再次接受新的存活對(duì)象。隨著時(shí)間的推移,伊甸區(qū)和 S0 中的某些對(duì)象被宣判死亡(不再被引用),并且伊甸區(qū)的可用空間也再次耗盡(填滿(mǎn)了),那么次要回收 又將再次被觸發(fā)。
對(duì)象年齡增長(zhǎng)
這一次,伊甸區(qū)和 S0 中的死亡和存活的對(duì)象會(huì)被做上標(biāo)記。其中,伊甸區(qū)的存活對(duì)象會(huì)被移動(dòng)到 S1,并且年齡增加至 1。S0 中的存活對(duì)象也會(huì)被移動(dòng)到 S1,并且年齡增加至 2(因?yàn)樗鼈兺^(guò)了兩次次要回收)。此時(shí),伊甸區(qū)和 S0 又是空的了。每次次要回收之后,伊甸區(qū)和兩個(gè)幸存者區(qū)中的一個(gè)都會(huì)是空的。
新對(duì)象總是在伊甸區(qū)被創(chuàng)建,周而復(fù)始。當(dāng)下一次垃圾回收發(fā)生時(shí),伊甸區(qū)和 S1 都會(huì)被清理,它們中的存活對(duì)象會(huì)被移動(dòng)到 S0 區(qū)。每次次要回收之后,這兩個(gè)幸存者區(qū)(S0 和 S1)就會(huì)交換一次。
額外年齡增長(zhǎng)
這個(gè)過(guò)程會(huì)一直進(jìn)行下去,直到某個(gè)存活對(duì)象的年齡達(dá)到了某個(gè)閾值,然后它就會(huì)被移動(dòng)到一個(gè)叫做“老年代”的地方,這是通過(guò)一個(gè)叫做“晉升”的過(guò)程來(lái)完成的。
使用 -Xmn 選項(xiàng)可以設(shè)置新生代的大小。
老年代
這個(gè)區(qū)域存放著那些挺過(guò)了許多次次要回收,并且達(dá)到了某個(gè)年齡閾值的對(duì)象。
晉升
在上面這個(gè)示例圖表中,晉升的年齡閾值為 8。在老年代發(fā)生的垃圾回收被稱(chēng)為 “主要回收Major GC”。(LCTT 譯注:也被稱(chēng)為“全回收Full GC”)
使用 -Xms 和 -Xmx 選項(xiàng)可以分別設(shè)置堆內(nèi)存大小的初始值和最大值。(LCTT 譯注:結(jié)合上面的 -Xmn 選項(xiàng),就可以間接設(shè)置老年代的大小了。)
永久代
永久代存放著一些元數(shù)據(jù),它們與應(yīng)用程序、Java 標(biāo)準(zhǔn)環(huán)境以及 JVM 自用的庫(kù)類(lèi)及其方法相關(guān)。JVM 會(huì)在運(yùn)行時(shí),用到了什么類(lèi)和方法,就會(huì)填充相應(yīng)的數(shù)據(jù)。當(dāng) JVM 發(fā)現(xiàn)有未使用的類(lèi),就會(huì)卸載或是回收它們,從而為正在使用的類(lèi)騰出空間。
使用 -XX:PermGen 和 -XX:MaxPerGen 選項(xiàng)可以分別設(shè)置永久代大小的初始值和最大值。
元空間
Java 8 引入了元空間Metaspace,并用它替換了永久代。這么做的好處是自動(dòng)調(diào)整大小,避免了 內(nèi)存不足OutOfMemory(OOM)錯(cuò)誤。
總結(jié)
本文討論了各種不同的 JVM 內(nèi)存“代”,以及它們是如何在分代垃圾回收算法中起作用的。對(duì)于程序員來(lái)說(shuō),掌握 Java 的內(nèi)存管理機(jī)制并不是必須的,但它能夠幫助你更好地理解 JVM 處理程序中的變量和類(lèi)實(shí)例的方式。這種理解使你能夠規(guī)劃和排除代碼故障,并理解特定平臺(tái)固有的潛在限制。
當(dāng)前標(biāo)題:JVM垃圾回收的工作原理
網(wǎng)站地址:http://www.fisionsoft.com.cn/article/cdspoio.html


咨詢(xún)
建站咨詢(xún)
