本节介绍影响GC的其它情况。
有些应用程序会通过 Finalization 和 弱引用、软引用 及 幻象引用 与 GC 交互。这些特性会在 Java 编程语言级别造成性能问题。一个例子就是 依赖 Finalization 关闭文件描述符。它使得一项外部资源(描述符)依赖于 GC 的及时性。依赖 GC 来管理内存之外的资源几乎总是个坏主意。
前言中与本节相关的文档中包含了一篇文章,它深入讨论了 Finalization 的陷阱,以及避免这些陷阱的技巧。
应用程序与 GC 交互的另一种方式是通过调用 System.gc() 来显式调用 Full GC。这可能会在不需要的时候(如,当一次 Minor GC 就足够的时候),强制完成一次 Major GC。这通常是应该避免的。显式 GC 的性能影响可通过使用标识 -XX:+DisableExplicitGC 禁用它们来衡量。该标识会导致 JVM 忽略对 System.gc() 的调用。
显式 GC 最常见的一种用法是 远程方法调用(RMI)的 分布式GC(DGC)。使用 RMI 的应用程序会引用其它 JVM 中的对象。如果不偶尔调用本地堆的 GC,这些应用程序中的垃圾就无法被回收。所以 RMI 会周期性地强制执行 Full GC。可通过以下属性来控制这些 GC 的频率:
java -Dsun.rmi.dgc.client.gcInterval=3600000
-Dsun.rmi.dgc.server.gcInterval=3600000 ...
该示例指定 每小时执行一次显式GC。默认频率是 每分钟执行一次。然而这也可能导致某些对象需要更长的时间才会被回收。这些属性的值最高可以被设置为 Long.MAX_VALUE。如果不对 DGC 活动时间设置上限,它会使得 显式GC 的实际时间间隔 为无限长。
软引用在 服务端VM 中存活的时间比客户端长。其清除频率可通过命令行选项 -XX:SoftRefLRUPolicyMSPerMB=
在 Java Hotspot VM 中,Java 类有一个内部表现形式,它被称为 类元数据。在 Java Hotspot VM 的早期版本中,类元数据 被分配在一个称为 “永久代” 的区域中。在 JDK 8 中,“永久代” 被删除,类元数据被分配到本地内存中(native memory)。默认情况下,可用于存储 类元数据 的本地内存(native memory)是没有限制的。可使用选项 MaxMetaspaceSize 来设置存放 类元数据 的本地内存上限。
Java Hotspot VM 会显式管理 元数据 空间。它会向操作系统申请空间,然后将这些空间分成块。类加载器 会在这些块中为 元数据 分配空间(一个块会被绑定到一个特定的类加载器。即,一个块最多只能同时对应一个类加载器)。当类被卸载后,其对应的块会被回收复用,或还给操作系统。元数据 使用 mmap 分配空间,而不是 malloc。
如果启用了 UseCompressedOops 且 使用了 UseCompressedClassesPointers,那么两个逻辑上不同的本地内存区域会被用于存放 类元数据。UseCompressedClassPointers 使用 32-bit 偏移量来表示 64-bit 进程中的 类指针。这如同 UseCompressedOops 对 Java 对象引用的作用。JVM 会分配一个区域来存放这些压缩后的 类指针。可用 CompressedClassSpaceSize 来设置该区域的大小,其默认值为 1GB。JVM 会在初始化时通过 mmap 为压缩后的类指针分配一块预留空间,并根据需要提交空间申请。MaxMetaspaceSize 是针对 已压缩的类指针 和 其它类指针 的空间总和。
类元数据 会在其对应的 Java类 卸载时被释放。Java类的卸载是通过 GC 实现的。为了卸载类并释放类元数据,可能会触发 GC。当类元数据使用的空间达到某个级别(高水位线)时,将引发 GC。GC 结束后,JVM 会根据 类元数据 释放的空间量 来提高或降低 “高水位线”。为避免过早地引发下一次 GC,“高水位线” 可能会被提高。“高水位线” 的初始值可通过命令行选项 MetaspaceSize 来设置。“高水位线” 的提高或降低会基于选项 MaxMetaspaceFreeRatio 和 MinMetaspaceFreeRatio 来确定。如果类元数据空间的当前可用占比大于 MaxMetaspaceFreeRatio,那么 “高水位线” 会被降低;如果小于 MinMetaspaceFreeRatio,那么 “高水位线” 会被提高。
为 MetaspaceSize 指定一个更高的值可以避免因 类元数据 过早引发 GC。为应用程序的类元数据分配多大的空间取决于应用程序自身特征,而且没有通用准则。MetaspaceSize 的默认值取决于平台,范围是 12MB ~ 20MB。
元数据所用空间的信息会包含在堆的打印输出中。
典型的堆打印输出:
Heap
PSYoungGen total 10752K, used 4419K
[0xffffffff6ac00000, 0xffffffff6b800000, 0xffffffff6b800000)
eden space 9216K, 47% used
[0xffffffff6ac00000,0xffffffff6b050d68,0xffffffff6b500000)
from space 1536K, 0% used
[0xffffffff6b680000,0xffffffff6b680000,0xffffffff6b800000)
to space 1536K, 0% used
[0xffffffff6b500000,0xffffffff6b500000,0xffffffff6b680000)
ParOldGen total 20480K, used 20011K
[0xffffffff69800000, 0xffffffff6ac00000, 0xffffffff6ac00000)
object space 20480K, 97% used
[0xffffffff69800000,0xffffffff6ab8add8,0xffffffff6ac00000)
Metaspace used 2425K, capacity 4498K, committed 4864K, reserved 1056768K
class space used 262K, capacity 386K, committed 512K, reserved 1048576K
在 “Metaspace” 开头的行中:
“class space” 开头的行中各数值是与压缩后的类指针相关的相应空间大小。