谭浩的博客

Simple is beauty.

Java 内存模型

Java 内存模型

正如上图所示,JVM 内存被分为两个区域。广义上来讲,实际上是 JVM 的堆内存被分为两部分:新生代老年代

Java 内存管理——新生代

新生代用于存储所有新建的对象。当新生代满了的时候则执行垃圾回收。这个垃圾回收又称为 Minor GC,新生代被划分为三个部分- Eden Memory 和两个Survivor Memory

新生代的重点:

  • 大部分新创建的对象都位于 Eden Memeory空间
  • Eden空间满了的时候,则触发Minor GC并切所有幸存者对象都被移动到其中一个幸存区
  • Minor GC也会检查幸存者对象,并且移动它们到一个幸存者区域。因此有时幸存者区域有一个总为空的。
  • 对象在多次GC之后依旧存活下来,将被移动到老年代。通常,它是通过设置一个年龄域值来决定新生代对象变为老年代对象。

Java 内存管理——老年代

老年代区域保存长久存活经过多次Minor GC的对象。通常当老年代区域充满的时候会触发一次GC。老年代的GC又被称为Major GC并且通常花费较长时间。

“停止世界”

所有的GC操作都是“停止世界”事件,因为所有应用线程都会停止直到GC操作结束。新生代保存短时间存活的对象,Minor GC非常快因此应用不会被影响。然而Major GC需要花费很长时间因为其需要检测所有存活的对象。Major GC的次数应该尽可能被最小化,因为它会导致应用程序在GC过程中无法响应。

垃圾收集器需要花费的时间依赖于所采取的不同策略。这就是为什么很有必要采取监视和调整垃圾回收器以避免高响应应用程序中的超时。

Java 内存模型 ——永久代

永久代(Perm Gen)包含JVM用于在应用程序中描述类和方法的元数据。因此永久代不属于 Java 堆内存。永久代由 JVM 在运行时根据应用程序使用的类来填充。永久代也包含了 Java SE 库的类和方法。永久代对象在Major GC中被回收。

Java 内存模型——方法区

方法区是永久代的一部分,用于储存类结构以及方法和构造方法的代码。

Java 内存模型——内存池

内存池由 JVM 内存管理器创建,以便当对象实现支持时为妻创建不可变对象池。字符串常量池就是这种内存池的一个例子。内存池可以属于堆或者永久代,这依赖于JVM内存管理器的实现。

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

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

Java 内存模型——Java 栈内存

Java 栈内存用于线程的执行。它们包含了特定于方法的特点值从该方法应用堆对象的引用。

Java 内存管理——Java堆内存开关

Java 提供了许多内存开关,我们可以用于设置内存大小和比例。下面是一些常用的内存开关:

VM SWITCH 描述
-Xms 用于设置 JVM 开启时的初始堆大小
-Xmx 用于设置最大堆的大小
-Xmn 用于设置新生代大小,剩余空间即为老年代
-XX:PermGen 用于设置永久代内存的初始化大小
-XX:MaxPermGen 用于设置永久代的最大尺寸
-XX:SurvivorRatio 用于设置Eden空间和幸存者空间的比例。例如,如果新生代大小为10M并且VM设置为 -XX:SurvivorRatio=2,则为Eden空间保留5M,每个幸存者空间各2.5M。默认值为8。
-XX:NewRatio 用于设置老年代和新生代的比例。默认值为2。

Java 内存管理——Java 垃圾回收

Java 垃圾回收是一个从内存中识别以及删除未被使用的对象的过程,其释放空间用于未来分配新的对象。Java 语言本身最好的特性之一也为自动垃圾回收。不像其他编程语言需要手动的进行内存分配和释放。

垃圾回收器是一个在背后运行的程序,其在内存中查看对象,并找出未被程序中任何部分应用的对象。所有这些没有被引用的对象将被删除,空间将被用于其他对象的分配。

一个垃圾回收过程涉及到的步骤:

  • Mark:这是垃圾回收的第一步,用于标记哪些对象没被使用哪些对象正在被使用。从应用的根节点开始,标记可达的对象为存活的对象
  • Delete/Sweep:垃圾回收器删除无用的对象,回收要分配给其他对象的空闲空间。
  • Compacting:为了更好的性能,在删除无用对象之后,将所有的幸存者对象被移动到一起,这将增加为新对象分配内存的性能。

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

  • 首先不够高效j,因为大部分新创建的对象将变得不可用。
  • 其次是在多次垃圾回收中存活的对象更有可能在未来依然被使用

上面的原因正是Java垃圾回收是分代的,我们在堆内存上分为新生代和老年代。

Java 内存管理——垃圾回收类型

在我们的应用程序中我们可以使用五种垃圾回收类型。我们只需要使用VM 开关去启用不同垃圾回收策略。

  • Serial GC (-XX:+UseSerialGC):Serail GC使用简单的标记-扫描-压缩方法用于新生代和老年代垃圾回收。Serial GC 在单机应用并且机器拥有较小CPU上非常有用,它适用于内存占用较少的应用。
  • Parallel GC(-XX:+UseParallelGC):Parallel GC和Serial GC表现基本一样除了在新生代垃圾回收时多线程,(多线程的数量极为CPU的核数)。我们可以通过-XX:ParallelGCThread=n控制线程数量。Parallel GC 又被称为吞吐量垃圾收集器,因为其使用多线程提高GC性能。Parallel GC 使用单线程用于老年代垃圾回收。
  • Parallel Old GC(-XX:+UseParallelOldGC):这个GC类似于Parallel GC除了其在老年代GC时也使用多线程。
  • Concurrent Mark Sweep(CMS)Collector(-XX:+UseConcMarkSweepGC):CMS垃圾回收器也被称为并发低暂停垃圾收集器。它用于老年代的垃圾回收。CMS垃圾回收器尝试最小化垃圾回收过程中的等待时间,通过和应用线程同时工作。CMS 垃圾回收器在在新生代使用Parallel GC相同的算法。该GC适用于无法长时间等待的高响应应用。我们可以使用-XX:ParallelCMSThreads=n限制CMS回收器的线程数量。

  • G1 Garbage Collector(-XX:+UseG1GC):G1垃圾回收器是在Java7中开始使用的,它的长远目标在于替代CMS垃圾回收器。G1收集器是并行,并发和递增压缩的低暂停垃圾收集器。G1垃圾收集器不像其他垃圾收集器其没有新生代和老年代空间的概念。它将堆空间分成多个同等大小的堆区域。当一垃圾收集被调用,它首先收集较少存活数据的区域。

Java 内存管理——Java 垃圾收集监控

我们可以使用Java命令行以及带有UI的工具监控一个应用的垃圾回收活动。

Java 垃圾收集调整

Java 垃圾收集调整是我们用于提高应用程序的吞吐量的最后选项。只有当你看到由于较长的GC时间导致应用程序超时影响性能时采用。

例如出现java.lang.OutOfMemoryError: PermGen space,则可以尝试使用-XX:PermGen-XX:MaxPermGenJVM选项监视和增加Perm Gen内存空间。也可以尝试使用-XX:+ CMSClassUnloadingEnabled并检查CMS垃圾收集器的性能。如果看到很多Full GC的操作,那么应该尝试增加老年代内存空间。整体的垃圾收集调整需要花费大量和时间和精力,并没有严格的规则,需要尝试不同的选项找到合适的垃圾收集配置。