在上一章中,我们了解了各种代GC。在本章中将讨论如何调整GC。
堆大小是Java应用程序性能的重要因素。如果它太小,那么它将经常被填充,因此,必须经常由GC收集。另一方面,如果只增加堆的大小,虽然它需要不那么频繁地收集,但是暂停的长度会增加。
此外,增加堆大小会对底层操作系统造成严重损害。使用分页,操作系统使应用程序看到的内存比实际可用内存多得多。操作系统通过使用磁盘上的一些交换空间来管理它,将程序的非活动部分复制到其中。当需要这些部分时,OS会将它们从磁盘复制回内存。
假设一台机器有8G的内存,而JVM看到16G的虚拟内存,JVM不会知道系统上实际上只有8G可用。它只会从操作系统请求16G,一旦获得该内存,它将继续使用它。操作系统必须交换大量数据,这对系统造成了巨大的性能损失。
然后是在这种虚拟内存的完整GC期间发生的暂停。由于GC将在整个堆上进行收集和压缩,因此必须等待很多时间才能将虚拟内存换出磁盘。在并发收集器的情况下,后台线程将不得不等待将数据从交换空间复制到内存。
所以这里是应该如何决定最佳堆大小的问题。第一条规则是永远不要求OS比实际存在的内存更多。这将完全防止频繁交换的问题。如果机器安装并运行了多个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
值决定 -
(total heap size) / (newRatio + 1)
由于newRatio
的初始值为2
,因此上述公式将YG的初始值赋予总堆大小的1/3
。始终可以通过使用NewSize
标志显式指定YG的大小来覆盖此值。此标志没有任何默认值,如果没有明确设置,YG的大小将继续使用上面的公式计算。
permagen
和元空间(Metaspace)是堆区域,JVM保存类的元数据。这个空间在Java 7中称为“permagen”,在Java 8中,它被称为“metaspace”。编译器和运行时使用此信息。
可以使用以下标志控制permagen
的大小:-XX:PermSize = N
和-XX:MaxPermSize = N
。可以使用以下方法控制元空间的大小:-XX:Metaspace- Size = N
和-XX:MaxMetaspaceSize = N
。
在未设置标志值时,如何管理permagen和元空间存在一些差异。默认情况下,两者都具有默认的初始大小。但是,虽然元空间可以占用所需的堆,但permagen可以占用不超过默认的初始值。例如,64b JVM具有82M的堆空间作为最大permagen大小。
请注意,由于元空间可以占用无限量的内存,除非另有指定,否则可能会出现内存不足错误。每当调整这些区域的大小时,都会发生完整的GC。因此,在启动期间,如果有许多类被加载,则元空间可以继续调整大小,从而每次都生成一个完整的GC。因此,如果初始元空间大小太小,则需要花费大量时间来启动大型应用程序。增加初始大小是一个好主意,因为它可以减少启动时间。
尽管permagen和metaspace保存了类元数据,但它并不是永久性的,并且GC会回收空间,就像对象一样。这通常用于服务器应用程序。无论何时对服务器进行新的部署,都必须清除旧的元数据,因为新的类加载器现在需要空间。GC释放了这个空间。