HotSpot虚拟机垃圾收集优化教程-垃圾收集器实现

垃圾收集器实现

Java SE平台的一个优点是他可以将开发人员从复杂的内存分配和垃圾收集中解放出来。
然而,当垃圾收集是主要的瓶颈时,理解垃圾收集实现的某些方面是很有用的。垃圾收集器对应用程序使用对象的方式进行猜想,这些猜想反应在可调参数中,这些参数可以在不牺牲抽象能力的情况下进行调整以提高性能。

_话题_

  • 分代垃圾收集
  • 分代
  • 性能考量
  • 吞吐量和空间量衡量

分代垃圾收集

当一个对象不能从运行程序中任何其他活动对象的任何引用中访问的时候,他会被认为是一个垃圾,虚拟机将可以重用他的内存。
理论上,最直接的垃圾收集算法在每次运行时都会遍历每个可访问的对象。任何遗留的对象都被视为垃圾。这种方式所需的时间和活动对象的数量成正比,这对于维护大量活动数据的大型应用程序来说是不能接受的。
Java HotSpot虚拟机合并了许多不同的垃圾收集算法,这些算法都使用了一种称为分代收集的技术。虽然简单的垃圾收集每次都会检测堆中的每个存活对象,但分代垃圾收集利用大多数应用程序的几个经验性观察到的属性来最小化回收未使用(垃圾)对象所需的工作。这些观察到的特性中最重要的是弱世代假说,即大多数对象只存活很短的一段时间。
图3-1中的蓝色区域是对象生命周期的典型分布。x轴显示在分配的字节中衡量对象的生命周期。Y轴上的字节计数是具有相应生命周期的对象的总字节数。左侧的尖峰表示分配后不久可以回收的对象(换句话说,已经死亡)。例如,迭代对象通常只在一个循环期间是存活的。

有些对象确实可以存活的更久,因此分布向右延伸。例如,通常有一些对象在初始化时分配,直到虚拟机退出才死亡。在这两个极端中间是一些在中间计算期间存在的对象,这里被看作是初始峰值右侧的块。有些应用程序具有非常不同的外观分布,但是令人惊讶的,大量应用程序拥有这种一般的形状。通过关注大多数对象在年轻代死亡的事实,高效率的收集被认为是有可能的。

分代

为了优化这个场景,内存是分代管理的(内存池存储不同年龄的对象)。垃圾收集发生在分代填满的时候。
绝大多数对象被分配到一个专用于年轻对象(年轻代)的池中,并且大多数对象在年轻代死亡。当年轻代填满时,他会导致一个minor垃圾收集,其中只有年轻一代会被收集,其他代的垃圾不会被收集。这种收集的成本与收集的活跃对象的数量成正比;充满死亡的对象的年轻代很快被收集。通常,在每次minor收集过程中,年轻代中幸存对象的一部分会被移到年老代。最终,年老代将被填满且必须被收集,从而导致一个major收集,这种情况下,整个堆都会被收集。major收集通常比minor收集持续更长时间,因为涉及的对象数量要多得多。图3-2显示了默认的串行垃圾收集器的分代管理。

在启动时,Java HotSpot虚拟机会将整个Java堆保留在地址空间中,但除非需要,否则不会为其分配任何的物理内存。覆盖Java堆的整个地址空间在逻辑上被分为年轻代和年老代。为对象内存保留的完整地址控制可以分为年轻和年老代。
年轻代由eden和两个survivor空间组成。大多数对象最初是在eden中分配的。一个survivor空间在任何时候都是空的,在垃圾收集期间作为eden和另外一个survivor空间中活动对象的目的地;垃圾收集后,eden和原来的survivor空间将会是空的。在下次垃圾收集中,两个survivor空间的用途将会被交换。最近填充的一个空间是复制到另一个survivor空间的活动对象的源。活动对象以这种方式在survivor空间之间被复制,直到他们被复制一定的次数或者没有足够的空间。这些活动对象将被拷贝到年老代。这个过程叫做老化(aging)。

性能考量

垃圾回收的主要衡量指标是吞吐量和延迟。

  • 吞吐量是长时间内未使用于垃圾收集的总时间的百分比。吞吐量包含分配内存所花费的时间(不过通常不需要调整分配的速度)。
  • 延迟是应用程序响应度的表现,垃圾收集暂停会影响应用程序的响应性。

用户对垃圾收集有不同的需求。例如,有些用户认为web服务器的正确度量值是吞吐量,因为垃圾收集期间的暂停可能是可以容忍的,或者只是被网络延迟所掩盖。然而,在交互式图形程序中,即使是短暂的暂停时间可能对用户体验产生负面的影响。
有些用户对其他考虑因素很敏感。空间量是进程的工作集,以页和缓存线为衡量。在具有有限物理内存或者多处理器的系统上,空间量可能可伸缩。Promptness是对象死亡到内存可用之间的时间,这是分布式系统的一个重要考量因素,包括RMI。
一般来说,为特定的分代选择大小是这些考虑因素之间的权衡。例如,一个非常大的年轻代可以提升吞吐量,但是这是以牺牲空间量,Promptness和暂停时间为代价的。年轻代的暂停时间可以缩小通过使用一个小的年轻代空间,并且以牺牲吞吐量为代价。分代的大小不会影响垃圾收集的频率和暂停时间。
没有一种绝对正确的方法可以选择分代的大小。最佳选择取决于应用程序使用内存的方式以及用户需求。因此,虚拟机对垃圾收集器的选择并不总是最优的,可能会被命令行选项覆盖;请参阅影响垃圾收集性能的因素。

吞吐量和空间量衡量

吞吐量和空间量是最好的衡量标度特定于应用程序。
例如,可以使用客户机负载生成器来测试web服务器的吞吐量。但是,通过检查虚拟机本身的诊断输出,可以很容易地估计由于垃圾收集而导致的暂停。命令行选项-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()触发的全收集。每一行以应用程序启动时开始的时间为时间戳。接下来是关于此行日志的级别(info)和标识(gc)信息。后面是gc的识别号。在这个例子中,有三条gc记录,编码分别为36,37和38。然后记录gc的类型和声明gc的原因。之后,会记录一些有关内存消耗的信息。使用日志格式被使用的内存在gc前->被使用的内存在gc后。
在示例的第一行中,这是239M->59M(307M),这意味着在gc之前有239M在使用,gc清除了大部分内存,但任然有57M存活。这个堆的大小在307M。注意,在这个例子中,全gc将堆的大小从307M缩小到104M。在内存使用信息之后,将记录gc的开始时间和结束时间以及持续时间。
-verbose:gc命令是-Xlog:gc的别名。-Xlog在HotSpot虚拟机中记录日志的常规日志配置选项。这是一个基于标记的系统,其中gc是标记之一。要获取有关gc正在执行的操作的更多信息,可以配置日志记录以打印具有gc标记和任何其他标记的任何信息。命令行选项是-Xlog:gc*。
下面是一个用G1垃圾收集器年轻代收集的例子-Xlog: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
FavoriteLoading添加本文到我的收藏
  • Trackback 关闭
  • 评论 (1)
    • qingdidi
    • 2020/02/02 8:34下午

    一般来说,为特定的分代选择大小是这些考虑因素之间的权衡。例如,一个非常大的年轻代可以提升吞吐量,但是这是以牺牲空间量,Promptness和暂停时间为代价的。年轻代的暂停时间可以缩小通过使用一个小的年轻代空间,并且以牺牲吞吐量为代价。分代的大小不会影响垃圾收集的频率和暂停时间。

    最后一句话看着为什么和前面看的感觉冲突呢,谢谢!

您必须 登陆 后才能发表评论

return top