了解JVM内存模型、Java内存管理对于理解Java垃圾回收的工作原理非常重要。今天我们将深入探讨Java中的内存管理、JVM内存的不同部分以及如何监视和执行垃圾回收调优。
Java(JVM)内存模型
正如您在上图中所见,JVM内存被分成了不同的部分。在广义上,JVM堆内存在物理上被划分为两个部分 – 年轻代和老年代。
Java中的内存管理 – 年轻代
年轻代是所有新对象被创建的地方。当年轻代填满时,将执行垃圾回收。这种垃圾回收称为Minor GC。年轻代被划分为三个部分 – 伊甸园内存和两个幸存者内存空间。关于年轻代空间的重要点:
- 大部分新建立的物件都位於伊甸記憶體空間。
- 當伊甸空間充滿物件時,會執行次要GC,並將所有倖存的物件移動到其中一個存活區。
- 次要GC還會檢查存活的物件並將它們移動到另一個存活區。因此,一次只會有一個存活區是空的。
- 在許多GC周期後存活下來的物件會被移動到老年代記憶體空間。通常,這是通過設置年輕代物件的年齡閾值來執行的,使其有資格晉升為老年代。
Java 中的記憶體管理 – 老年代
老年代記憶體包含了經過多輪次次要GC後仍存活的物件。通常,當老年代記憶體滿時會執行垃圾收集。老年代垃圾收集被稱為主要GC,通常需要較長的時間。
停止世界事件
所有的垃圾收集都是“停止世界”的事件,因为所有应用程序线程都会在操作完成之前停止。由于年轻代保留了短期对象,所以小型GC非常快速,应用程序不会受到影响。然而,主要GC需要很长时间,因为它会检查所有活动对象。应该尽量减少主要GC,因为它会使应用程序在垃圾收集期间不响应。因此,如果您有一个响应迅速的应用程序并且有很多主要垃圾收集发生,您会注意到超时错误。垃圾收集器的持续时间取决于垃圾收集的策略。这就是为什么有必要监控和调整垃圾收集器,以避免高度响应的应用程序中出现超时的原因。
Java内存模型 – 永久代
永久代或“Perm Gen”包含JVM描述应用程序中使用的类和方法所需的应用程序元数据。请注意,Perm Gen不是Java堆内存的一部分。Perm Gen是由JVM在运行时根据应用程序使用的类填充的。Perm Gen还包含Java SE库类和方法。Perm Gen对象在完全垃圾收集中进行垃圾收集。
Java內存模型 – 方法區
方法區是Perm Gen中的一部分空間,用於存儲類結構(運行時常量和靜態變量)以及方法和構造函數的代碼。
Java內存模型 – 內存池
內存池是由JVM內存管理器創建的,如果實現支持,則創建一個不可變對象池。字符串池是這種內存池的一個很好的例子。內存池可以屬於Heap或Perm Gen,具體取決於JVM內存管理器的實現。
Java內存模型 – 運行時常量池
運行時常量池是每個類的運行時常量池在類中的表示。它包含類的運行時常量和靜態方法。運行時常量池是方法區的一部分。
Java Memory Model – Java Stack Memory
Java 堆疊記憶體用於執行緒的執行。它們包含特定於方法的值,這些值存活時間短,以及從方法引用的堆中的其他物件的引用。你應該閱讀堆疊記憶體與堆記憶體之間的差異。
Java 中的記憶體管理 – Java 堆記憶體開關
Java 提供了許多記憶體開關,我們可以用來設置記憶體大小及其比例。一些常用的記憶體開關包括:
VM Switch | VM Switch Description |
---|---|
-Xms | For setting the initial heap size when JVM starts |
-Xmx | For setting the maximum heap size. |
-Xmn | For setting the size of the Young Generation, rest of the space goes for Old Generation. |
-XX:PermGen | For setting the initial size of the Permanent Generation memory |
-XX:MaxPermGen | For setting the maximum size of Perm Gen |
-XX:SurvivorRatio | For providing ratio of Eden space and Survivor Space, for example if Young Generation size is 10m and VM switch is -XX:SurvivorRatio=2 then 5m will be reserved for Eden Space and 2.5m each for both the Survivor spaces. The default value is 8. |
-XX:NewRatio | For providing ratio of old/new generation sizes. The default value is 2. |
大多數情況下,上述選項已足夠,但如果您還想查看其他選項,請參閱JVM 選項官方頁面。
Java中的内存管理 – Java垃圾回收
Java垃圾回收是识别并从内存中删除未使用的对象的过程,并释放空间以供未来处理中创建的对象分配使用。Java编程语言的一个最佳特性是自动垃圾回收,与其他编程语言(如C)不同,其中内存分配和释放是一个手动过程。垃圾收集器是在后台运行的程序,它查看内存中的所有对象,并找出未被程序的任何部分引用的对象。所有这些未引用的对象都将被删除,并为分配给其他对象而回收空间。垃圾收集的基本方式之一包括三个步骤:
- 标记:这是第一步,垃圾收集器识别出哪些对象正在使用,哪些对象未被使用。
- 正常删除:垃圾收集器移除未使用的对象并回收空闲空间,以供分配给其他对象使用。
- 带压缩的删除:为了提高性能,在删除未使用的对象后,所有幸存下来的对象可以被移动到一起。这将增加内存分配给新对象的性能。
简单的标记和删除方法存在两个问题。
- 第一个问题是,这不够高效,因为大多数新创建的对象将变得无用。
- 其次,被多次垃圾回收循环使用的对象很可能在将来的循环中继续被使用。
上述简单方法的这些缺点是Java垃圾回收是分代的的原因,我们在堆内存中有年轻代和老年代空间。我已经解释了对象是如何根据Minor GC和Major GC从一个代空间移动到另一个代空间的。
Java内存管理 – Java垃圾回收类型
我们可以在应用程序中使用五种垃圾回收类型。我们只需要使用JVM开关来启用应用程序的垃圾回收策略。让我们逐一看看每一种。
- 串行GC(-XX:+UseSerialGC):串行GC使用简单的标记-清除-整理方法进行年轻代和老年代的垃圾回收,即Minor和Major GC。串行GC适用于客户端机器,如我们的简单独立应用程序和CPU较小的机器。它适用于内存占用较低的小型应用程序。
- Parallel GC (-XX:+UseParallelGC): 平行GC與串行GC相同,只是它為年輕代垃圾回收生成N個線程,其中N是系統中CPU核心的數量。我們可以使用
-XX:ParallelGCThreads=n
JVM選項來控制線程數量。平行垃圾收集器也被稱為吞吐量收集器,因為它使用多個CPU加速GC性能。平行GC對於舊代垃圾收集使用單個線程。 - Parallel Old GC (-XX:+UseParallelOldGC): 這與平行GC相同,只是它對年輕代和舊代垃圾收集都使用多個線程。
- Concurrent Mark Sweep (CMS) Collector (-XX:+UseConcMarkSweepGC): CMS收集器也被稱為並發低暫停收集器。它對舊代進行垃圾收集。CMS收集器試圖通過與應用程序線程同時進行大部分垃圾收集工作來將由於垃圾收集而導致的暫停最小化。CMS收集器在年輕代使用與平行收集器相同的算法。這種垃圾收集器適用於對長暫停時間沒有辦法的響應式應用程序。我們可以使用
-XX:ParallelCMSThreads=n
JVM選項來限制CMS收集器中的線程數量。 - G1垃圾收集器(-XX:+UseG1GC):Garbage First或G1垃圾收集器自Java 7起可用,其长期目标是取代CMS收集器。G1收集器是一种并行、并发且增量压缩的低停顿垃圾收集器。Garbage First收集器的工作方式与其他收集器不同,没有年轻代和老年代空间的概念。它将堆空间分成多个大小相等的堆区域。在调用垃圾收集时,它首先收集具有较少活动数据的区域,因此称为“Garbage First”。您可以在Garbage-First Collector Oracle Documentation中找到更多详细信息。
Java中的内存管理 – Java垃圾收集监视
我們可以使用Java命令行以及UI工具來監控應用程序的垃圾收集活動。 例如,我正在使用Java SE下載提供的演示應用程序之一。 如果您想使用相同的應用程序,請轉到Java SE下載頁面並下載JDK 7和JavaFX演示和樣本。 我正在使用的示例應用程序是Java2Demo.jar,位於jdk1.7.0_55/demo/jfc/Java2D
目錄中。 但是這是一個可選步驟,您可以為任何Java應用程序運行GC監控命令。 我使用的命令來啟動演示應用程序是:
pankaj@Pankaj:~/Downloads/jdk1.7.0_55/demo/jfc/Java2D$ java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar
jstat
我們可以使用jstat
命令行工具來監控JVM內存和垃圾收集活動。 它隨標準JDK一起提供,因此您無需進行其他操作即可獲取它。 要執行jstat
,您需要知道應用程序的進程ID,您可以輕鬆地使用ps -eaf | grep java
命令獲取它。
pankaj@Pankaj:~$ ps -eaf | grep Java2Demo.jar
501 9582 11579 0 9:48PM ttys000 0:21.66 /usr/bin/java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar
501 14073 14045 0 9:48PM ttys002 0:00.00 grep Java2Demo.jar
因此,我的Java應用程序的進程ID為9582。 現在,我們可以執行jstat命令如下。
pankaj@Pankaj:~$ jstat -gc 9582 1000
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
1024.0 1024.0 0.0 0.0 8192.0 7933.3 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8026.5 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8030.0 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8122.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8171.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 48.7 0.0 8192.0 106.7 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
1024.0 1024.0 48.7 0.0 8192.0 145.8 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
jstat的最後一個參數是每次輸出之間的時間間隔,因此它將每1秒打印一次內存和垃圾收集數據。 讓我們逐個查看每一列。
- S0C和S1C:此列顯示Survivor0和Survivor1區域的當前大小(以KB為單位)。
- S0U和S1U:此列顯示Survivor0和Survivor1區域的當前使用情況(以KB為單位)。 請注意,其中一個存活區域始終是空的。
- EC 和 EU:这些列显示了 Eden 空间的当前大小和使用情况(以 KB 为单位)。请注意,EU 的大小正在增加,一旦超过 EC,就会调用 Minor GC,并减少 EU 的大小。
- OC 和 OU:这些列显示了老年代的当前大小和当前使用情况(以 KB 为单位)。
- PC 和 PU:这些列显示了 Perm Gen 的当前大小和当前使用情况(以 KB 为单位)。
- YGC 和 YGCT:YGC 列显示了发生在年轻代的 GC 事件的次数。YGCT 列显示了年轻代 GC 操作的累积时间。请注意,在 EU 值由于小型 GC 而下降的同一行中,它们都会增加。
- FGC 和 FGCT:FGC 列显示了发生的 Full GC 事件的次数。FGCT 列显示了 Full GC 操作的累积时间。请注意,与年轻代 GC 时间相比,完全 GC 时间太长。
- GCT:该列显示了 GC 操作的总累积时间。请注意,它是 YGCT 和 FGCT 列值的总和。
使用 jstat 的优点是它也可以在远程服务器上执行,而无需 GUI。请注意,S0C、S1C 和 EC 的总和为 10m,正如通过 -Xmn10m
JVM 选项指定的那样。
Java VisualVM 和 Visual GC
如果你想要在 GUI 中查看記憶體和 GC 操作,那麼你可以使用 jvisualvm
工具。Java VisualVM 也是 JDK 的一部分,因此你不需要單獨下載它。只需在終端中運行 jvisualvm
命令來啟動 Java VisualVM 應用程序。一旦啟動,你需要從工具 -< 插件選項中安裝 Visual GC 插件,如下圖所示。 安裝完 Visual GC 後,只需在左側列中打開應用程序,然後轉到 Visual GC 部分。你將會得到一張顯示 JVM 記憶體和垃圾回收詳細信息的圖像,如下圖所示。
Java 垃圾回收調優
Java垃圾回收调优应该是增加应用程序吞吐量的最后选择,只有当您发现由于较长的GC时间导致应用程序超时而导致性能下降时才应使用。如果在日志中看到java.lang.OutOfMemoryError: PermGen space
错误,请尝试监视并增加Perm Gen内存空间,使用-XX:PermGen和-XX:MaxPermGen JVM选项。您还可以尝试使用-XX:+CMSClassUnloadingEnabled
并检查CMS垃圾收集器的性能如何。如果看到大量的Full GC操作,那么您应该尝试增加旧代内存空间。总的来说,垃圾回收调优需要大量的工作和时间,而且没有硬性规则。您需要尝试不同的选项并进行比较,以找到最适合您的应用程序的最佳选项。关于Java内存模型,Java内存管理和垃圾收集,就介绍到这里,希望它有助于您理解JVM内存和垃圾回收过程。
Source:
https://www.digitalocean.com/community/tutorials/java-jvm-memory-model-memory-management-in-java