垃圾回收之分代假設

標記-清除(mark and Sweep)是最經典的垃圾回收算法

碎片整理

內存模型就像一個衣柜有很多隔斷,假如每個隔斷內部都是滿的,如果執行清理,那么最后可能內存會很亂,一三五層隔斷有東西,二四六沒東西,或者更亂。

大家可以想象一下,假如要寫入一個很大的文件,需要一個連續的內存地址來存儲。如果內存碎片很多,可能在尋找地址的時候就會浪費很多時間。

所以在JVM每次執行清理的時候會執行內存清理整理,以減少內存碎片。

分代假設

對象越多則收集垃圾的消耗時間越長,所以研究人員發現,程序中的大多可回收的垃圾歸為兩類:

  • 大部分很快就不使用或立即無用
  • 存活時間很長的

如果統一去處理這些垃圾,可能就會消耗更長的時間處理存活較長的垃圾,所以這些觀測形成了弱代假設。基于這一假設,VM的內存分為,年輕代(Young)和老年代(Old/Tenured)

拆分這兩個可清理的單獨區域,允許采取不同的算法從而來大幅提高GC的性能。

但是這種方法不是沒有問題,例如,不同分代中的對象可能會互相引用,這樣在收集某個分代可能就是GC root。
沒有完美的垃圾回收算法,只有好的算法,之所以這樣說是因為,GC算法專門針對“要么死得快,要么活的長”這類特征的對像來優化,JVM對于那些不長不短的對象就很<font color=red>awkword(尷尬)</font>

內存池(Memory Pools)

Java內存模型是很抽象的,很多定義也不同,網上資料各異沒有明確官方定義,但是小編感覺,只要理解是對的,有助你Java開發,那么怎么理解怎么記,就算是錯的,也會增加你對Java垃圾回收的理解,這里用一個圖來把這個抽象的東西,表述一下。


新生代(Eden,伊甸園)

Eden是內存中的一個區域,用來分配新創建的對象。通常會與多個線程同時創建多個對象,所以Eden區被劃分為多一個線程本地分配緩沖區,通過分離,避免與其他線程同步操作。


如果線程緩沖區沒有足夠內存了,就會在Eden共享區分配,如果共享區也沒有了內存,就會觸發年輕代的GC來釋放內存。如果GC之后Eden區依然沒有足夠的內存,則對象就分配到年老代,Old

當Eden去進行垃圾收集的時候,GC將從root可達的對象過一遍,并標記存活對象,標記完成后,Eden中所有存活的對象,被復制到Eden的右邊的存活區(Survivor spaces),這個時候Eden區可能就是空的,然后分配新對象,這種方法就是標記-復制,是復制而不是移動。

存活區(Survivor Spaces)

Eden 區的旁邊是兩個存活區, 稱為 from 空間和 to 空間。需要著重強調的的是, 任意時刻總有一個存活區是空的(empty)。

空的這個存活區在下一次年輕代GC是存放對象,年輕代中所有的存活對象(包括Edenq區和非空的那個 “from” 存活區)都會被復制到 ”to“ 存活區。GC過程完成后, ”to“ 區有對象,而 ‘from’ 區里沒有對象。兩者的角色進行正好切換 。

存活的對象會在倆個區多次復制,當存活區對象在經歷多次GC后依然,存活那么這個時候就可以升級為老年代Old。為了確定一個對象是否“足夠老”, 可以被提升(Promotion)到老年代,GC模塊跟蹤記錄每個存活區對象存活的次數。每次分代GC完成后,存活對象的年齡就會增長。當年齡超過提升閾值(tenuring threshold), 就會被提升到老年代區域。

具體的提升閾值由JVM動態調整,但也可以用參數-XX:+MaxTenuringThreshold 來指定上限。如果設置 -XX:+MaxTenuringThreshold=0, 則GC時存活對象不在存活區之間復制,直接提升到老年代。

現代 JVM 中這個閾值默認設置為15個 GC周期。這也是HotSpot中的最大值。

如果存活區空間不夠存放年輕代中的存活對象,提升(Promotion)也可能更早地進行。

老年代(Old Generation)

老年代的對象是經歷了篩選進來的,老年代內存空間通常會更大,一般是垃圾的概率會相對小。

老年代的GC發生頻率比年輕代小很多,因為大部分都是存活的,所以使用的算法也不是標記和復制,對于因為是垃圾的改變會相對小,所以如果說是清理垃圾,更不說是,優化內存。但是都會經歷下面幾步:

  • 通過標志位(marked bit),標記所有通過 GC roots 可達的對象.
  • 刪除所有不可達對象
  • 整理老年代空間中的內容,方法是將所有的存活對象復制,從老年代空間開始的地方,依次存放。

永久代(PermGen)

在Java 8 之前有一個特殊的空間,稱為“永久代”(Permanent Generation)。這是存儲元數據(metadata)的地方,比如 class 信息等。此外,這個區域中也保存有其他的數據和信息, 包括 內部化的字符串(internalized strings)等等。實際上這給Java開發者造成了很多麻煩,因為很難去計算這塊區域到底需要占用多少內存空間。預測失敗導致的結果就是產生 java.lang.OutOfMemoryError: Permgen space 這種形式的錯誤。除非 ·OutOfMemoryError· 確實是內存泄漏導致的,否則就只能增加 permgen 的大小,例如下面的示例,就是設置 permgen 最大空間為 256 MB:

java -XX:MaxPermSize=256m com.mycompany.MyApplication

元數據區(Metaspace)

既然估算元數據所需空間那么復雜, Java 8直接刪除了永久代(Permanent Generation),改用 Metaspace。從此以后, Java 中很多雜七雜八的東西都放置到普通的堆內存里。

當然,像類定義(class definitions)之類的信息會被加載到 Metaspace 中。元數據區位于本地內存(native memory),不再影響到普通的Java對象。默認情況下, Metaspace的大小只受限于 Java進程可用的本地內存。這樣程序就不再因為多加載了幾個類/JAR包就導致 java.lang.OutOfMemoryError: Permgen space. 。注意, 這種不受限制的空間也不是沒有代價的 —— 如果 Metaspace 失控, 則可能會導致很嚴重的內存交換(swapping), 或者導致本地內存分配失敗。

如果需要避免這種最壞情況,那么可以通過下面這樣的方式來限制 Metaspace 的大小, 如 256 MB:

java -XX:MaxMetaspaceSize=256m com.mycompany.MyApplication

Minor GC vs Major GC vs Full GC

以下內容原文寫的很清楚,是概念上內容,采用原文描述
垃圾收集事件(Garbage Collection events)通常分為: 小型GC(Minor GC) - 大型GC(Major GC) - 和完全GC(Full GC) 。本節介紹這些事件及其區別。然后你會發現這些區別也不是特別清晰。

最重要的是,應用程序是否滿足 服務級別協議(Service Level Agreement, SLA), 并通過監控程序查看響應延遲和吞吐量。也只有那時候才能看到GC事件相關的結果。重要的是這些事件是否停止整個程序,以及持續多長時間。

雖然 Minor, Major 和 Full GC 這些術語被廣泛應用, 但并沒有標準的定義, 我們還是來深入了解一下具體的細節吧。

小型GC(Minor GC)

年輕代內存的垃圾收集事件稱為小型GC。這個定義既清晰又得到廣泛共識。對于小型GC事件,有一些有趣的事情你應該了解一下:
- 當JVM無法為新對象分配內存空間時總會觸發 Minor GC,比如 Eden 區占滿時。所以(新對象)分配頻率越高, Minor GC 的頻率就越高。
- Minor GC 事件實際上忽略了老年代。從老年代指向年輕代的引用都被認為是GC Root。而從年輕代指向老年代的引用在標記階段全部被忽略。
- Minor GC 每次都會引起全線停頓(stop-the-world ), 暫停所有的應用線程。對大多數程序而言,暫停時長基本上是可以忽略不計的, 因為 Eden 區的對象基本上都是垃圾, 也不怎么復制到存活區/老年代。如果情況不是這樣, 大部分新創建的對象不能被垃圾回收清理掉, 則 Minor GC的停頓就會持續更長的時間。

所以 Minor GC 的定義很簡單 —— Minor GC 清理的就是年輕代。

Major GC vs Full GC

Minor GC 清理的是年輕代空間(Young space),相應的,其他定義也很簡單:

  • Major GC(大型GC) 清理的是老年代空間(Old space)。
  • Full GC(完全GC)清理的是整個堆, 包括年輕代和老年代空間。

悲劇的情況出現了,很多Major GC是由于Minor GC觸發的,所以很多情況下這和Full GC就沒有多大區別了,這個就很<font color=red>awkword(尷尬)</font>

這也讓我們認識到,不應該去操心是叫 Major GC 呢還是叫 Full GC, 我們應該關注的是: 某次GC事件 是否停止所有線程,或者是與其他線程并發執行。

文章參考自附錄地址,附帶小編自己的學習經歷和理解重新翻譯,希望大家共進步

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內容