Java(JVM)内存模型

理解JVM内存模型

Java内存管理是理解Java垃圾回收工作原理的重要基础。今天,我们将探讨Java中的内存管理、JVM内存的不同部分,以及如何监控和调优垃圾回收。

Java (JVM) 内存模型

Java-Memory-Model-450x186.png

如上图所示,JVM内存分为多个部分。从广义上讲,JVM堆内存被物理地分为两部分:年轻代(Young Generation)和老年代(Old Generation)。

Java中的内存管理 - 年轻代

年轻代是创建所有新对象的地方。当年轻代填满时,会进行垃圾回收,这种回收称为小垃圾回收(Minor GC)。年轻代分为三个部分:伊甸园内存(Eden Memory)和两个幸存者内存空间(Survivor Memory)。关于年轻代空间的重要点:

  • 大多数新创建的对象位于伊甸园内存空间。
  • 当伊甸园空间填满对象时,进行Minor GC,所有幸存对象被移动到其中一个幸存者空间。
  • Minor GC还检查幸存对象并将其移动到另一个幸存者空间。因此在任何时候,某个幸存者空间始终为空。
  • 经过多次GC循环幸存的对象将移动到老年代内存空间。通常,通过设置年轻代对象的年龄阈值来决定它们何时可以提升到老年代。

Java中的内存管理 - 老年代

老年代内存包含长寿命对象,这些对象在多轮Minor GC后仍然存活。通常,当老年代内存满时,会进行垃圾回收,称为大垃圾回收(Major GC),通常需要更长时间。

停止世界事件

所有的垃圾回收都是“停止世界”事件,因为所有应用程序线程会在操作完成之前被停止。由于年轻代保留短期对象,Minor GC非常快,应用程序不会受到影响。然而,Major GC需要较长时间,因为它会检查所有活跃对象。应尽量减少Major GC,因为这会使应用程序在垃圾回收期间无响应。如果您的应用程序需要响应性而发生大量Major GC,您会注意到超时错误。垃圾收集器所需的时间取决于所使用的垃圾回收策略。因此,监控和调优垃圾收集器以避免高响应性应用中的超时是必要的。

Java内存模型 - 永久代

永久代(Permanent Generation或Perm Gen)包含JVM所需的应用程序元数据,以描述应用程序中使用的类和方法。请注意,Perm Gen并不是Java堆内存的一部分。Perm Gen由JVM在运行时根据应用程序使用的类填充,还包含Java SE库的类和方法。Perm Gen对象在完全垃圾回收中被收集。

Java内存模型 - 方法区

方法区是Perm Gen的一部分,用于存储类结构(运行时常量和静态变量)及方法和构造函数的代码。

Java内存模型 - 内存池

内存池是由JVM内存管理器创建的,用于创建不可变对象的池(如果实现支持)。字符串池是这种内存池的一个好例子。内存池可以属于堆或Perm Gen,这取决于JVM内存管理器的实现。

Java内存模型 - 运行时常量池

运行时常量池是每个类的运行时常量池的表示,包含类运行时常量和静态方法。运行时常量池是方法区的一部分。

Java内存模型 - Java栈内存

Java栈内存用于线程的执行,包含方法特定的短期值和对堆中其他对象的引用。您应该了解堆内存与栈内存之间的区别。

Java中的内存管理 - Java堆内存开关

Java提供了许多内存开关,我们可以用它们来设置内存大小及其比例。常用的内存开关包括:

VM开关VM开关描述
-Xms设置JVM启动时的初始堆大小
-Xmx设置最大堆大小
-Xmn设置年轻代的大小,其余空间用于老年代
-XX:PermGen设置永久代内存的初始大小
-XX:MaxPermGen设置Perm Gen的最大大小
-XX:SurvivorRatio提供伊甸园空间与幸存者空间的比例,例如,如果年轻代大小为10m且VM开关为-XX:SurvivorRatio=2,则将保留5m用于伊甸园空间,每个幸存者空间各保留2.5m。默认值为8。
-XX:NewRatio提供老代与新生代大小的比例。默认值为2。

大多数时候,上述选项是足够的,但如果您想查看其他选项,请查看JVM选项官方页面

Java中的内存管理 - Java垃圾回收

Java垃圾回收是识别并删除内存中未使用对象的过程,以释放空间供未来处理创建的对象。Java编程语言的一个最佳特性是自动垃圾回收,而不像其他编程语言(如C)那样需要手动管理内存分配和释放。垃圾收集器是在后台运行的程序,检查内存中的所有对象,找出未被程序任何部分引用的对象。所有这些未引用的对象都会被删除,并回收空间以分配给其他对象。垃圾回收的基本步骤包括:

  • 标记:这是垃圾收集器识别哪些对象在使用、哪些对象未使用的第一步。
  • 正常删除:垃圾收集器移除未使用的对象,回收空间以分配给其他对象。
  • 压缩删除:为了提高性能,在删除未使用对象后,可以将所有幸存对象一起移动,这将提高新对象的内存分配性能。

简单的标记和删除方法有两个问题:

  • 第一,它效率不高,因为大多数新创建的对象很快会变为未使用。
  • 第二,经过多次垃圾回收周期的对象很可能在未来的周期中仍然在使用。

上述简单方法的缺点是Java垃圾回收是代际的,因此我们在堆内存中有年轻代和老年代空间。我已经在上面解释了如何根据Minor GC和Major GC扫描和移动对象。

Java中的内存管理 - Java垃圾回收类型

我们可以在应用程序中使用五种垃圾回收类型。只需使用JVM开关启用应用程序的垃圾回收策略。让我们逐一看一下这些类型。

  • 串行GC (-XX:+UseSerialGC):使用简单的标记-清除-压缩方法进行年轻代和老年代垃圾回收,即Minor和Major GC。串行GC适用于客户端机器,如简单的独立应用程序和小型CPU的机器。适用于内存占用小的应用程序。
  • 并行GC (-XX:+UseParallelGC):与串行GC相同,但它为年轻代垃圾回收生成N个线程,其中N是系统中的CPU核心数量。我们可以使用-XX:ParallelGCThreads=nJVM选项设置n。对于大型应用程序(特别是高吞吐量应用程序),该方法比串行更有效,因为在小于CPU核心的机器上,它不会产生额外的开销。
  • 并发标记清除GC (-XX:+UseConcMarkSweepGC):具有最小的应用程序停机时间。该GC使用多个线程进行Minor GC,并使用两个阶段并发标记清除对象,几乎没有暂停。与并行GC相比,这种方式在高响应性应用程序中表现良好。
  • G1 GC (-XX:+UseG1GC):通过将堆划分为多个区域来工作,它将小部分空间分配给年轻代和老年代。它的垃圾回收过程分为多个阶段,通常表现良好。G1 GC在大规模应用程序中最有效,尤其是在大堆内存和大响应时间敏感应用程序中。我们还可以通过设置-XX:MaxGCPauseMillis=200来定义G1 GC的最大暂停时间。
  • ZGC (-XX:+UseZGC):适用于内存很大的应用程序,基本上旨在支持大规模应用程序。它在高响应性应用程序中表现良好,适合在处理大量内存时有超低停机时间。

以上是内存管理中的基本概念。下面我们将探讨如何监控和调优Java垃圾回收器,以便您可以优化应用程序性能并避免出现停机时间。

监控和调优垃圾回收

为了优化Java应用程序中的内存管理,您可以使用以下几种方法监控和调优垃圾回收。

  • 使用JVisualVM或其他监控工具以获取内存使用情况的详细视图。
  • 使用JConsole进行实时监控。
  • 收集垃圾回收日志以获取数据并分析堆内存使用情况。
  • 优化堆大小和年轻代和老年代的比例,以确保内存管理高效。
  • 减少创建短期对象以降低垃圾回收频率。

监控与调优是确保您的Java应用程序运行良好的关键部分。通过仔细分析和优化,您可以提升应用程序的性能。

综上所述,深入理解Java内存模型是提升Java应用性能的重要步骤。通过有效管理内存和优化垃圾回收,您将能够提高应用的响应性和稳定性。

 

若你想提升Java技能,可关注我们的Java培训课程。