HotSpot JVM 記憶體管理

富士康質檢員張全蛋發表於2020-12-17

關於 JVM 記憶體管理或者說垃圾收集,大家可能看過很多的文章了,筆者準備給大家總結下。這算是系列的第一篇,接下來一段時間會持續更新。

本文主要是翻譯《Memory Management in the Java HotSpot Virtual Machine》白皮書的前四章內容,這是 2006 的老文章了,當年釋出這篇文章的還是 Sun Microsystems,以後應該會越來越少人記得這家曾經無比偉大的公司了。

雖然這個白皮書有點老了,不過那個時候 Sun 在 J2SE 5.0 版本的 HotSpot 虛擬機器上已經有了 Parallel 並行垃圾收集器和 CMS 這種併發收集器了,所以其實內容也沒那麼過時。

其實本文應該有挺多人都翻譯過,我大體上是意譯的,增、刪了部分內容。

其他的知識,包括 Java5 之後的垃圾收集器,如 Java8 的 MetaSpace 取代了永久代、G1 收集器等,將在日後的文章中進行介紹。

 

垃圾收集概念


GC 需要做 3 件事情:

  • 分配記憶體,為每個新建的物件分配空間
  • 確保還在使用的物件的記憶體一直還在,不能把有用的空間當垃圾回收了
  • 釋放不再使用的物件所佔用的空間

我們把還被 GC Roots 引用的物件稱為活的,把不再被引用的物件認為是死的,也就是我們說的垃圾,GC 的工作就是找到死的物件,回收它們佔用的空間。

在這裡,我們總結一下 GC Roots 有哪些:

  • 當前各執行緒執行方法中的區域性變數(包括形參)引用的物件
  • 已被載入的類的 static 域引用的物件
  • 方法區中常量引用的物件
  • JNI 引用

以上不完全,不過我覺得了解到這些就夠了,瞭解更多

我們把 GC 管理的記憶體稱為 堆(heap),垃圾收集啟動的時機取決於各個垃圾收集器,通常,垃圾收集發生於整個堆或堆的部分已經被使用光了,或者使用的空間達到了某個百分比閾值。這些後面都會具體說,這裡的每一句話都是對應了某些場景的。

對於記憶體分配請求,實現的難點在於在堆中找到一塊沒有被使用的確定大小的記憶體空間。所以,對於大部分垃圾回收演算法來說避免記憶體碎片化是非常重要的,它將使得空間分配更加高效。

 

垃圾收集器的理想特徵


  1. 安全和全面:活的物件一定不能被清理掉,死的物件一定不能在幾個回收週期結束後還在記憶體中。
  2. 高效:不能將我們的應用程式掛起太長時間。我們需要在時間、空間、頻次上作出權衡。比如,如果堆記憶體很小,每次垃圾收集就會很快,但是頻次會增加。如果堆記憶體很大,很久才會被填滿,但是每一次回收需要的時間很長。
  3. 儘量少的記憶體碎片:每次將垃圾物件釋放以後,這些空間可能分佈在各個地方,最糟糕的情況就是,記憶體中到處都是碎片,在給一個大物件分配空間的時候沒有記憶體可用,實際上記憶體是夠的。消除碎片的方式就是壓縮
  4. 可擴充套件性:在多核多執行緒應用中,記憶體分配和垃圾回收都不應該成為可擴充套件性的瓶頸。原文提到的這一點,我的理解是:單執行緒垃圾回收在多核系統中會浪費 CPU 資源,如果我理解錯誤,請指正我。

 

設計上的權衡


往下看之前,我們需要先分清楚這裡的兩個概念:併發和並行

  • 並行:多個垃圾回收執行緒同時工作,而不是隻有一個垃圾回收執行緒在工作
  • 併發垃圾回收執行緒和應用程式執行緒同時工作,應用程式不需要掛起

在設計或選擇垃圾回收演算法的時候,我們需要作出以下幾個權衡:

  • 序列 vs 並行

    序列收集的情況,即使是多核 CPU,也只有一個核心參與收集。使用並行收集器的話,垃圾收集的工作將分配給多個執行緒在不同的 CPU 上同時進行。並行可以讓收集工作更快,缺點是帶來的複雜性和記憶體碎片問題。

  • 併發 vs Stop-the-world

    當 stop-the-world 垃圾收集器工作的時候,應用將完全被掛起。與之相對的,併發收集器在大部分工作中都是併發進行的,也許會有少量的 stop-the-world。

    stop-the-world 垃圾收集器比並發收集器簡單很多,因為應用掛起後堆空間不再發生變化,它的缺點是在某些場景下掛起的時間我們是不能接受的(如 web 應用)。

    相應的,併發收集器能夠降低掛起時間,但是也更加複雜,因為在收集的過程中,也會有新的垃圾產生,同時,需要有額外的空間用於在垃圾收集過程中應用程式的繼續使用。

  • 壓縮 vs 不壓縮 vs 複製

    當垃圾收集器標記出記憶體中哪些是活的,哪些是垃圾物件後,收集器可以進行壓縮,將所有活的物件移到一起,這樣新的記憶體分配就可以在剩餘的空間中進行了。經過壓縮後,分配新物件的記憶體空間是非常簡單快速的。

    相對的,不壓縮的收集器只會就地釋放空間,不會移動存活物件。優點就是快速完成垃圾收集,缺點就是潛在的碎片問題。通常,這種情況下,分配物件空間會比較慢比較複雜,比如為新的一個大物件找到合適的空間。

    還有一個選擇就是複製收集器,將活的物件複製到另一塊空間中,優點就是原空間被清空了,這樣後續分配物件空間非常迅速,缺點就是需要進行復制操作和佔用額外的空間。

相關文章