JVM(十一):記憶體分配
- 2019 年 10 月 3 日
- 筆記
JVM(十一):記憶體分配
在前面的章節中,我們花了大量的篇幅去介紹 JVM 內的記憶體布局、對象在記憶體中的狀態、垃圾回收的演算法和具體實現等。今天讓我們探討一下對象是如何分配記憶體的。
堆記憶體劃分
前面說過,從記憶體回收的角度來看,堆被分為以下幾個部分:
開始創建的對象大多都會直接分配到新生代一塊區域中,只有大對象和經過多次 GC 後依然存活的對象會放置在老年代中。
那麼一個對象在被創建出來後,何時存在與哪個記憶體區域中呢,其具體的分配策略又是什麼呢?下面就讓我們一起來看一下幾個具體的記憶體分配策略,了解一個對象在產生後該何去何從。
對象優先在Eden分配
首先,我們先複習一下 GC 的兩種分類:
Minor GC,又叫新生代GC:發生在新生代的 GC,由於大多數 Java 對象都是朝生夕死的,因此發生這種 GC 十分的頻繁,回收速度一般也會比較快。
Full/Major GC,又叫老年代GC:發生在老年代的 GC,一般會比 Minor GC 慢 10倍以上。
在大多數情況下,對象優先在 Eden 區進行分配。當 Eden 區空間不足時,將會發起一次 Minor GC。
大對象直接進入老年代
首先,我們來看一下什麼是大對象,其一般是指需要大量連續記憶體空間的 Java 對象。最典型的就是很長的字元串和數組。對虛擬機來說大對象是很難處理的對象,因為虛擬機需要一塊連續的大空間進行分配,不過更加難以處理的是朝生夕死的大對象,經常出現的大對象,導致頻繁地觸發 GC 來空出大量連續空間來安置它們,影響了性能。因此,這種情況也警醒開發人員在開發過程中要盡量避免這種問題。
長期存活對象進入老年代
因為 Java 的垃圾回收是採取的分代收集思想。因此新生代的對象也有一定的途徑進入老年代,對應的就是存活時間,為了定位每個對象的存活時間,JVM 給每個對象定義了一個對象年齡計數器,當對象在 Survivor 區每熬過一次 Minor GC,對象的年齡就 +1,當對象的年齡滿足一定的條件(默認為15),就將其晉陞到老年代。
前面這種晉陞老年代的條件顯得比較的死板。因此在 JVM 中還有一種靈活的方式:如果在 Survivor 空間中相同年齡的所有對象綜合大於 Survivor 空間的一半,那麼年齡大於等於該閥值的對象就可以直接進入老年代,而無須等到滿足要求的年齡。
空間分配擔保
上面說過,在新生代經過 Minor GC 存活的對象,滿足一定的條件會進入老年代 , 因此就需要保證老年代有充足的空間能夠分配 。這種擔保方式讓 JVM 在進行 Full GC 的時候,可以進行大膽的操作 (因為 Full GC 十分的耗時,影響性能, 因此在 Minor GC 前需要進行一定的判斷)。
上圖就是空間分配擔保的流程,此種方式可以避免 Full GC 過於頻繁,影響性能。
總結
在本文中,我們詳細講述了對象記憶體分配的一些通用策略。了解這些策略可以讓我們明白 JVM 分配對象的邏輯,寫出更加高效健壯的程式碼。
文章在公眾號「iceWang」第一手更新,有興趣的朋友可以關注公眾號,第一時間看到筆者分享的各項知識點,謝謝!筆芯!
本系列文章主要借鑒自《深入分析 JavaWeb 技術內幕》和《深入理解 Java 虛擬機-JVM 高級特性與最佳實踐》。