Java 虚拟机 - 调整 GC


在上一章中,我们了解了各种 Generational Gc。在本章中,我们将讨论如何调整 GC。

堆大小

堆大小是影响 Java 应用程序性能的重要因素。如果它太小,那么它会经常被填满,因此,GC 必须经常收集它。另一方面,如果我们只是增加堆的大小,尽管需要不太频繁地收集它,但暂停的时间会增加。

此外,增加堆大小会对底层操作系统造成严重影响。使用分页,操作系统使我们的应用程序看到比实际可用的内存多得多的内存。操作系统通过使用磁盘上的一些交换空间,将程序的非活动部分复制到其中来管理此操作。当需要这些部分时,操作系统将它们从磁盘复制回内存。

假设一台机器有 8G 内存,JVM 看到 16G 虚拟内存,JVM 不会知道系统上实际上只有 8G 可用。它只会向操作系统请求 16G,一旦获得该内存,它将继续使用它。操作系统将不得不交换大量数据进出,这对系统来说是一个巨大的性能损失。

然后是在此类虚拟内存的完整 GC 期间发生的暂停。由于GC会作用于整个堆进行收集和压缩,因此它必须等待很长时间才能将虚拟内存从磁盘换出。在并发收集器的情况下,后台线程将不得不等待很长时间才能将数据从交换空间复制到内存。

那么这里就出现了我们应该如何决定最佳堆大小的问题。第一条规则是永远不要向操作系统请求比实际存在的内存更多的内存。这样就完全避免了频繁交换的问题。如果计算机安装并运行多个 JVM,那么所有这些 JVM 的总内存请求量将小于系统中实际存在的 RAM。

您可以使用两个标志来控制 JVM 的内存请求大小 -

  • -XmsN - 控制请求的初始内存。

  • -XmxN - 控制可以请求的最大内存。

这两个标志的默认值取决于底层操作系统。例如,对于在 MacOS 上运行的 64b JVM,-XmsN = 64M 且 -XmxN = 至少 1G 或总物理内存的 1/4。

请注意,JVM 可以自动在两个值之间进行调整。例如,如果它注意到发生了太多 GC,只要内存大小低于 -XmxN 并且满足所需的性能目标,它就会不断增加内存大小。

如果您确切知道应用程序需要多少内存,则可以设置 -XmsN = -XmxN。在这种情况下,JVM 不需要找出堆的“最佳”值,因此 GC 过程变得更加高效。

世代规模

您可以决定要将多少堆分配给 YG,以及要将多少堆分配给 OG。这两个值都会通过以下方式影响我们应用程序的性能。

如果 YG 的大小很大,那么收集的频率就会降低。这将导致升级到 OG 的对象数量减少。另一方面,如果将 OG 的大小增加太多,那么收集和压缩它会花费太多时间,这会导致 STW 长时间暂停。因此,用户必须在这两个值之间找到平衡。

以下是可用于设置这些值的标志 -

  • -XX:NewRatio=N: YG 与 OG 的比率(默认值 = 2)

  • -XX:NewSize=N: YG的初始大小

  • -XX:MaxNewSize=N: YG的最大尺寸

  • -XmnN:使用此标志将 NewSize 和 MaxNewSize 设置为相同的值

YG 的初始大小由 NewRatio 的值通过给定公式确定 -

(总堆大小)/(newRatio + 1)

由于newRatio的初始值为2,因此上式给出YG的初始值为总堆大小的1/3。您始终可以通过使用 NewSize 标志显式指定 YG 的大小来覆盖此值。该标志没有任何默认值,如果没有显式设置,YG 的大小将继续使用上述公式计算。

Permagen 和元空间

永久元和元空间是 JVM 保存类元数据的堆区域。该空间在 Java 7 中称为“permagen”,在 Java 8 中称为“元空间”。该信息由编译器和运行时使用。

您可以使用以下标志控制永久文件的大小:-XX: PermSize=N-XX:MaxPermSize=N。元空间的大小可以使用-XX:Metaspace- Size=N-XX:MaxMetaspaceSize=N来控制。

当未设置标志值时,永久元和元空间的管理方式存在一些差异。默认情况下,两者都有默认的初始大小。但是,虽然元空间可以根据需要占用尽可能多的堆,但永久元只能占用默认的初始值。例如,64b JVM 的最大永久大小为 82M 堆空间。

请注意,由于元空间可以占用无限量的内存(除非指定不这样做),因此可能会出现内存不足错误。每当调整这些区域的大小时,就会发生完整的 GC。因此,在启动过程中,如果有很多类被加载,元空间可能会不断调整大小,从而导致每次都发生完整的 GC。因此,如果初始元空间大小太小,大型应用程序需要花费大量时间来启动。增加初始大小是一个好主意,因为它可以减少启动时间。

虽然永久元和元空间保存类元数据,但它不是永久的,并且空间由 GC 回收,就像对象的情况一样。这通常是在服务器应用程序的情况下。每当您对服务器进行新部署时,都必须清理旧的元数据,因为新的类加载器现在需要空间。该空间由 GC 释放。