Java(JVM)內(nèi)存模型

理解JVM內(nèi)存模型

Java內(nèi)存管理是理解Java垃圾回收工作原理的重要基礎(chǔ)。今天,我們將探討Java中的內(nèi)存管理、JVM內(nèi)存的不同部分,以及如何監(jiān)控和調(diào)優(yōu)垃圾回收。

Java (JVM) 內(nèi)存模型

Java-Memory-Model-450x186.png

如上圖所示,JVM內(nèi)存分為多個(gè)部分。從廣義上講,JVM堆內(nèi)存被物理地分為兩部分:年輕代(Young Generation)和老年代(Old Generation)。

Java中的內(nèi)存管理 - 年輕代

年輕代是創(chuàng)建所有新對象的地方。當(dāng)年輕代填滿時(shí),會(huì)進(jìn)行垃圾回收,這種回收稱為小垃圾回收(Minor GC)。年輕代分為三個(gè)部分:伊甸園內(nèi)存(Eden Memory)和兩個(gè)幸存者內(nèi)存空間(Survivor Memory)。關(guān)于年輕代空間的重要點(diǎn):

  • 大多數(shù)新創(chuàng)建的對象位于伊甸園內(nèi)存空間。
  • 當(dāng)伊甸園空間填滿對象時(shí),進(jìn)行Minor GC,所有幸存對象被移動(dòng)到其中一個(gè)幸存者空間。
  • Minor GC還檢查幸存對象并將其移動(dòng)到另一個(gè)幸存者空間。因此在任何時(shí)候,某個(gè)幸存者空間始終為空。
  • 經(jīng)過多次GC循環(huán)幸存的對象將移動(dòng)到老年代內(nèi)存空間。通常,通過設(shè)置年輕代對象的年齡閾值來決定它們何時(shí)可以提升到老年代。

Java中的內(nèi)存管理 - 老年代

老年代內(nèi)存包含長壽命對象,這些對象在多輪Minor GC后仍然存活。通常,當(dāng)老年代內(nèi)存滿時(shí),會(huì)進(jìn)行垃圾回收,稱為大垃圾回收(Major GC),通常需要更長時(shí)間。

停止世界事件

所有的垃圾回收都是“停止世界”事件,因?yàn)樗袘?yīng)用程序線程會(huì)在操作完成之前被停止。由于年輕代保留短期對象,Minor GC非???,應(yīng)用程序不會(huì)受到影響。然而,Major GC需要較長時(shí)間,因?yàn)樗鼤?huì)檢查所有活躍對象。應(yīng)盡量減少M(fèi)ajor GC,因?yàn)檫@會(huì)使應(yīng)用程序在垃圾回收期間無響應(yīng)。如果您的應(yīng)用程序需要響應(yīng)性而發(fā)生大量Major GC,您會(huì)注意到超時(shí)錯(cuò)誤。垃圾收集器所需的時(shí)間取決于所使用的垃圾回收策略。因此,監(jiān)控和調(diào)優(yōu)垃圾收集器以避免高響應(yīng)性應(yīng)用中的超時(shí)是必要的。

Java內(nèi)存模型 - 永久代

永久代(Permanent Generation或Perm Gen)包含JVM所需的應(yīng)用程序元數(shù)據(jù),以描述應(yīng)用程序中使用的類和方法。請注意,Perm Gen并不是Java堆內(nèi)存的一部分。Perm Gen由JVM在運(yùn)行時(shí)根據(jù)應(yīng)用程序使用的類填充,還包含Java SE庫的類和方法。Perm Gen對象在完全垃圾回收中被收集。

Java內(nèi)存模型 - 方法區(qū)

方法區(qū)是Perm Gen的一部分,用于存儲(chǔ)類結(jié)構(gòu)(運(yùn)行時(shí)常量和靜態(tài)變量)及方法和構(gòu)造函數(shù)的代碼。

Java內(nèi)存模型 - 內(nèi)存池

內(nèi)存池是由JVM內(nèi)存管理器創(chuàng)建的,用于創(chuàng)建不可變對象的池(如果實(shí)現(xiàn)支持)。字符串池是這種內(nèi)存池的一個(gè)好例子。內(nèi)存池可以屬于堆或Perm Gen,這取決于JVM內(nèi)存管理器的實(shí)現(xiàn)。

Java內(nèi)存模型 - 運(yùn)行時(shí)常量池

運(yùn)行時(shí)常量池是每個(gè)類的運(yùn)行時(shí)常量池的表示,包含類運(yùn)行時(shí)常量和靜態(tài)方法。運(yùn)行時(shí)常量池是方法區(qū)的一部分。

Java內(nèi)存模型 - Java棧內(nèi)存

Java棧內(nèi)存用于線程的執(zhí)行,包含方法特定的短期值和對堆中其他對象的引用。您應(yīng)該了解堆內(nèi)存與棧內(nèi)存之間的區(qū)別。

Java中的內(nèi)存管理 - Java堆內(nèi)存開關(guān)

Java提供了許多內(nèi)存開關(guān),我們可以用它們來設(shè)置內(nèi)存大小及其比例。常用的內(nèi)存開關(guān)包括:

VM開關(guān)VM開關(guān)描述
-Xms設(shè)置JVM啟動(dòng)時(shí)的初始堆大小
-Xmx設(shè)置最大堆大小
-Xmn設(shè)置年輕代的大小,其余空間用于老年代
-XX:PermGen設(shè)置永久代內(nèi)存的初始大小
-XX:MaxPermGen設(shè)置Perm Gen的最大大小
-XX:SurvivorRatio提供伊甸園空間與幸存者空間的比例,例如,如果年輕代大小為10m且VM開關(guān)為-XX:SurvivorRatio=2,則將保留5m用于伊甸園空間,每個(gè)幸存者空間各保留2.5m。默認(rèn)值為8。
-XX:NewRatio提供老代與新生代大小的比例。默認(rèn)值為2。

大多數(shù)時(shí)候,上述選項(xiàng)是足夠的,但如果您想查看其他選項(xiàng),請查看JVM選項(xiàng)官方頁面。

Java中的內(nèi)存管理 - Java垃圾回收

Java垃圾回收是識(shí)別并刪除內(nèi)存中未使用對象的過程,以釋放空間供未來處理創(chuàng)建的對象。Java編程語言的一個(gè)最佳特性是自動(dòng)垃圾回收,而不像其他編程語言(如C)那樣需要手動(dòng)管理內(nèi)存分配和釋放。垃圾收集器是在后臺(tái)運(yùn)行的程序,檢查內(nèi)存中的所有對象,找出未被程序任何部分引用的對象。所有這些未引用的對象都會(huì)被刪除,并回收空間以分配給其他對象。垃圾回收的基本步驟包括:

  • 標(biāo)記:這是垃圾收集器識(shí)別哪些對象在使用、哪些對象未使用的第一步。
  • 正常刪除:垃圾收集器移除未使用的對象,回收空間以分配給其他對象。
  • 壓縮刪除:為了提高性能,在刪除未使用對象后,可以將所有幸存對象一起移動(dòng),這將提高新對象的內(nèi)存分配性能。

簡單的標(biāo)記和刪除方法有兩個(gè)問題:

  • 第一,它效率不高,因?yàn)榇蠖鄶?shù)新創(chuàng)建的對象很快會(huì)變?yōu)槲词褂谩?/li>
  • 第二,經(jīng)過多次垃圾回收周期的對象很可能在未來的周期中仍然在使用。

上述簡單方法的缺點(diǎn)是Java垃圾回收是代際的,因此我們在堆內(nèi)存中有年輕代和老年代空間。我已經(jīng)在上面解釋了如何根據(jù)Minor GC和Major GC掃描和移動(dòng)對象。

Java中的內(nèi)存管理 - Java垃圾回收類型

我們可以在應(yīng)用程序中使用五種垃圾回收類型。只需使用JVM開關(guān)啟用應(yīng)用程序的垃圾回收策略。讓我們逐一看一下這些類型。

  • 串行GC (-XX:+UseSerialGC):使用簡單的標(biāo)記-清除-壓縮方法進(jìn)行年輕代和老年代垃圾回收,即Minor和Major GC。串行GC適用于客戶端機(jī)器,如簡單的獨(dú)立應(yīng)用程序和小型CPU的機(jī)器。適用于內(nèi)存占用小的應(yīng)用程序。
  • 并行GC (-XX:+UseParallelGC):與串行GC相同,但它為年輕代垃圾回收生成N個(gè)線程,其中N是系統(tǒng)中的CPU核心數(shù)量。我們可以使用-XX:ParallelGCThreads=nJVM選項(xiàng)設(shè)置n。對于大型應(yīng)用程序(特別是高吞吐量應(yīng)用程序),該方法比串行更有效,因?yàn)樵谛∮贑PU核心的機(jī)器上,它不會(huì)產(chǎn)生額外的開銷。
  • 并發(fā)標(biāo)記清除GC (-XX:+UseConcMarkSweepGC):具有最小的應(yīng)用程序停機(jī)時(shí)間。該GC使用多個(gè)線程進(jìn)行Minor GC,并使用兩個(gè)階段并發(fā)標(biāo)記清除對象,幾乎沒有暫停。與并行GC相比,這種方式在高響應(yīng)性應(yīng)用程序中表現(xiàn)良好。
  • G1 GC (-XX:+UseG1GC):通過將堆劃分為多個(gè)區(qū)域來工作,它將小部分空間分配給年輕代和老年代。它的垃圾回收過程分為多個(gè)階段,通常表現(xiàn)良好。G1 GC在大規(guī)模應(yīng)用程序中最有效,尤其是在大堆內(nèi)存和大響應(yīng)時(shí)間敏感應(yīng)用程序中。我們還可以通過設(shè)置-XX:MaxGCPauseMillis=200來定義G1 GC的最大暫停時(shí)間。
  • ZGC (-XX:+UseZGC):適用于內(nèi)存很大的應(yīng)用程序,基本上旨在支持大規(guī)模應(yīng)用程序。它在高響應(yīng)性應(yīng)用程序中表現(xiàn)良好,適合在處理大量內(nèi)存時(shí)有超低停機(jī)時(shí)間。

以上是內(nèi)存管理中的基本概念。下面我們將探討如何監(jiān)控和調(diào)優(yōu)Java垃圾回收器,以便您可以優(yōu)化應(yīng)用程序性能并避免出現(xiàn)停機(jī)時(shí)間。

監(jiān)控和調(diào)優(yōu)垃圾回收

為了優(yōu)化Java應(yīng)用程序中的內(nèi)存管理,您可以使用以下幾種方法監(jiān)控和調(diào)優(yōu)垃圾回收。

  • 使用JVisualVM或其他監(jiān)控工具以獲取內(nèi)存使用情況的詳細(xì)視圖。
  • 使用JConsole進(jìn)行實(shí)時(shí)監(jiān)控。
  • 收集垃圾回收日志以獲取數(shù)據(jù)并分析堆內(nèi)存使用情況。
  • 優(yōu)化堆大小和年輕代和老年代的比例,以確保內(nèi)存管理高效。
  • 減少創(chuàng)建短期對象以降低垃圾回收頻率。

監(jiān)控與調(diào)優(yōu)是確保您的Java應(yīng)用程序運(yùn)行良好的關(guān)鍵部分。通過仔細(xì)分析和優(yōu)化,您可以提升應(yīng)用程序的性能。

綜上所述,深入理解Java內(nèi)存模型是提升Java應(yīng)用性能的重要步驟。通過有效管理內(nèi)存和優(yōu)化垃圾回收,您將能夠提高應(yīng)用的響應(yīng)性和穩(wěn)定性。

 

若你想提升Java技能,可關(guān)注我們的Java培訓(xùn)課程。