垃圾收集器实现

Java SE平台的一个优势是它可以保护开发人员免受内存分配和垃圾收集的复杂性的影响。

但是,当垃圾收集是主要瓶颈时,了解实现的某些方面很有用。

垃圾收集器对应用程序使用对象的方式做出假设,这些可以反映在可调参数中,可以调整这些参数以提高性能,而不会牺牲抽象的功能。

世代垃圾收集

对象被认为是垃圾,当运行程序中任何其他活动对象的任何引用无法再访问它时,VM可以重用它的内存。

理论上,最简单的垃圾收集算法每次运行时都会遍历每个可到达的对象。任何剩余的对象都被认为是垃圾。这种方法所花费的时间与活动对象的数量成正比,这对于维护大量实时数据的大型应用程序来说是不可行的。

Java HotSpot VM包含许多不同的垃圾收集算法,这些算法都使用称为世代集合的技术。

虽然天真(naive)垃圾收集每次都会检查堆中的每个活动对象,但是世代收集会利用大多数应用程序的几个经验观察属性来最小化回收未使用(垃圾)对象所需的工作。这些观察到的特性中最重要的是弱代世界假说,它表明大多数物体只能存活很短的时间。

图3-1中的蓝色区域是对象寿命的典型分布。 x轴显示以分配的字节为单位测量的对象生存期。 y轴上的字节数是具有相应生命周期的对象中的总字节数。左边的尖峰表示在分配后不久可以回收的物体(换句话说,已经“死亡”)。例如,迭代器对象通常仅在单个循环的持续时间内处于活动状态。

jsgct_dt_003_alc_vs_srvng.png

有些物体的寿命更长,因此分布向右延伸。

例如,通常在初始化时分配一些对象,这些对象一直存在直到VM退出。

在这两个极端之间是在一些中间计算期间存活的对象,在这里看作是初始峰值右侧的块。

一些应用具有非常不同的外观分布,但是令人惊讶的大数量具有这种一般形状。

通过关注大多数物体“年轻化”这一事实,可以实现高效收集。

世代(Generations)

为了优化这种情况,内存是在几代人中管理的(内存池包含不同年龄的对象)。当生成填满时,每一代都会发生垃圾收集。

绝大多数对象都分配在专用于年轻物体(年轻一代)的池中,并且大多数物体都在那里死亡。当年轻一代填满时,它会导致一个小集合,其中只收集年轻一代;其他几代的垃圾没有回收。这些收集的成本,按照第一顺序,与收集的活动对象的数量成比例;很快就会收集到充满死亡物体的年轻一代。通常,来自年轻一代的幸存物体的一部分在每个次要集合期间被移动到旧一代。最终,旧一代填满并且必须被收集,从而产生一个主要集合,其中收集整个堆。主要集合通常比次要集合持续更长时间,因为涉及的对象数量明显更多。

图3-2串行收集器中代的默认排列

jsgct_dt_001_armgnt_gn_new.png

在启动时,Java HotSpot VM会将整个Java堆保留在地址空间中,但除非需要,否则不会为其分配任何物理内存。

覆盖Java堆的整个地址空间在逻辑上分为年轻代和旧代。为对象存储器保留的完整地址空间可以分为年轻代和旧代。

年轻一代由伊甸园和两个幸存者空间组成。大多数对象最初都是在伊甸园中分配的。

一个幸存者空间在任何时候都是空的,并且在垃圾收集期间充当伊甸园和其他幸存者空间中的活物体的目的地;

垃圾收集后,伊甸园和源幸存者空间都是空的。

在下一个垃圾收集中,交换两个幸存者空间的目的。

最近填充的一个空间是复制到另一个幸存者空间的活动对象的来源。以这种方式在幸存者空间之间复制对象,直到它们被复制了一定次数或者没有足够的空间存在。这些对象将复制到旧区域中。此过程也称为老化。

性能注意事项

垃圾收集的主要措施是吞吐量和延迟。

  • 吞吐量是长时间未考虑的垃圾收集总时间的百分比。吞吐量包括分配所花费的时间(但通常不需要调整分配速度)。

  • 延迟是应用程序的响应性。垃圾收集暂停会影响应用程序的响应能力。

用户对垃圾收集有不同的要求。例如,有些人认为Web服务器的正确度量是吞吐量,因为垃圾收集期间的暂停可能是可以容忍的,或者只是被网络延迟所掩盖。但是,在交互式图形程序中,即使短暂停顿也可能对用户体验产生负面影响。

一些用户对其他考虑因素很敏感。足迹是一个过程的工作集,以页面和缓存行来衡量。在具有有限物理内存或许多进程的系统上,占用空间可能会影响可伸缩性。提示性是指对象变为死亡和内存可用之间的时间,这是分布式系统的一个重要考虑因素,包括远程方法调用(RMI)。

通常,选择特定代的尺寸是这些考虑因素之间的权衡。例如,非常大的年轻一代可以最大化吞吐量,但这样做是以占用空间,及时性和暂停时间为代价的。年轻一代的停顿可以通过使用小型年轻代来减少吞吐量。一代的大小不会影响另一代的收集频率和暂停时间。

没有一种正确的方法可以选择一代人的规模。最佳选择取决于应用程序使用内存的方式以及用户要求。因此,虚拟机对垃圾收集器的选择并不总是最佳的,可以使用命令行选项覆盖;查看影响垃圾收集性能的因素。

吞吐量和足迹测量

使用特定于应用程序的度量来最好地测量吞吐量和占用空间。

例如,可以使用客户端负载生成器测试Web服务器的吞吐量,而可以使用pmap命令在Solaris操作系统上测量服务器的占用空间。

但是,通过检查虚拟机本身的诊断输出,可以轻松估计由于垃圾回收导致的暂停。

命令行选项 -verbose:gc 打印有关每个集合的堆和垃圾回收的信息。

这是一个例子:

[15,651s][info ][gc] GC(36) Pause Young (G1 Evacuation Pause) 239M->57M(307M) (15,646s, 15,651s) 5,048ms
[16,162s][info ][gc] GC(37) Pause Young (G1 Evacuation Pause) 238M->57M(307M) (16,146s, 16,162s) 16,565ms
[16,367s][info ][gc] GC(38) Pause Full (System.gc()) 69M->31M(104M) (16,202s, 16,367s) 164,581ms

输出显示两个年轻的集合,后跟一个完整的集合,该集合由应用程序调用System.gc()启动。

行以时间戳开头,表示应用程序启动后的时间。接下来是有关此行的日志级别(信息)和标记(gc)的信息。随后是GC识别号。

在这种情况下,有三个GC,编号为36,37和38.然后记录GC的类型和说明GC的原因。

在此之后,将记录有关内存消耗的一些信息。该日志使用 “used before GC” -> “used after GC” (“heap size”)。

在该示例的第一行中,这是239M-> 57M(307M),这意味着在GC之前使用了239 MB并且GC清除了大部分内存,但是57 MB存活了。堆大小为307 MB。

请注意,在此示例中,完整的GC将堆从307 MB缩小到104 MB。在内存使用信息之后,将记录GC的开始和结束时间以及持续时间(结束-开始)。

-verbose:gc 命令是 -Xlog:gc 的别名。

-Xlog是用于登录HotSpot JVM的常规日志记录配置选项。它是一个基于标签的系统,其中gc是标签之一。

要获取有关GC正在执行的操作的更多信息,您可以配置日志记录以打印具有gc标记和任何其他标记的任何消息。

这个命令行选项是-Xlog:gc *

以下是使用-Xlog记录的一个G1年轻集合的示例:gc *:

[10.178s][info][gc,start ] GC(36) Pause Young (G1 Evacuation Pause) 
[10.178s][info][gc,task ] GC(36) Using 28 workers of 28 for evacuation 
[10.191s][info][gc,phases ] GC(36) Pre Evacuate Collection Set: 0.0ms
[10.191s][info][gc,phases ] GC(36) Evacuate Collection Set: 6.9ms 
[10.191s][info][gc,phases ] GC(36) Post Evacuate Collection Set: 5.9ms 
[10.191s][info][gc,phases ] GC(36) Other: 0.2ms 
[10.191s][info][gc,heap ] GC(36) Eden regions: 286->0(276) 
[10.191s][info][gc,heap ] GC(36) Survivor regions: 15->26(38)
[10.191s][info][gc,heap ] GC(36) Old regions: 88->88 
[10.191s][info][gc,heap ] GC(36) Humongous regions: 3->1 
[10.191s][info][gc,metaspace ] GC(36) Metaspace: 8152K->8152K(1056768K)
[10.191s][info][gc ] GC(36) Pause Young (G1 Evacuation Pause) 391M->114M(508M) 13.075ms 
[10.191s][info][gc,cpu ] GC(36) User=0.20s Sys=0.00s Real=0.01s

TODO

GC 日志的查看和分析

参考资料

https://docs.oracle.com/javase/9/gctuning/garbage-collector-implementation.htm