【HotSpot虚拟机垃圾回收调优指南】3.垃圾收集器的实现

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

但是,当垃圾收集成为主要瓶颈时,理解实现的某些方面是非常有用的。垃圾收集器对应用程序使用对象的方式进行了假设,这些假设反映在可调参数中,这些参数可以在不牺牲抽象功能的情况下进行调整,以提高性能。

目录

一.分代垃圾收集

二.分代

三.性能注意事项

四.吞吐量和占用空间测量


一.分代垃圾收集

当一个对象从正在运行的程序中的任何其他活动对象的引用中无发访问(没有任何引用关系)时,这个对象被认为是垃圾,VM可以重用它的内存。

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

Java HotSpot VM包含了多种不同的垃圾收集算法,它们全都使用了一种称为分代收集的技术。虽然每次都要检查堆中的每个活动对象,但是分代收集利用几个经验观察大多数应用程序的属性来最小化回收未使用(垃圾)对象所需的工作这些观察到的特性中,最重要的是弱世代假说,该假说认为大多数对象只能存活很短的一段时间。(原文:The Java HotSpot VM incorporates a number of different garbage collection algorithms that all use a technique called generational collection. While naive garbage collection examines every live object in the heap every time, generational collection exploits several empirically observed properties of most applications to minimize the work required to reclaim unused (garbage) objects. The most important of these observed properties is the weak generational hypothesis, which states that most objects survive for only a short period of time.)

 下图中的蓝色区域是对象寿命的典型分布。X轴是以分配的字节为单位显示对象生命周期。Y轴上的字节计数是具有相应生存期的对象中的总字节数。左边的尖峰代表分配后不久可以回收的对象(换句话说,已经“死亡”的)。例如,迭代器对象通常只在单个循环期间活动。(原文:The blue area in Figure 3-1 is a typical distribution for the lifetimes of objects. The x-axis shows object lifetimes measured in bytes allocated. The byte count on the y-axis is the total bytes in objects with the corresponding lifetime. The sharp peak at the left represents objects that can be reclaimed (in other words, have "died") shortly after being allocated. For example, iterator objects are often only alive for the duration of a single loop.)

Description of Figure 3-1 follows

有些对象寿命更长,所以分布向右延伸。例如,通常在初始化时分配的某些对象一直存在,直到VM退出。在这两个极端之间是一些处于中间计算阶段的对象,在这里被看作是初始峰值右侧的块。有些应用程序具有非常不同的外观分布,但令人惊讶的是,大量应用程序具有这种一般形状。通过关注大多数对象“死得早”这一事实,高效的收集成为可能。

二.分代

为了针对这种情况进行优化,内存分几代进行管理(内存池保存不同年龄的对象)。垃圾收集发生在每一代填满时。

绝大多数对象都分配在一个专门用于年轻对象(年轻代)的池中,并且大多数对象都死在那里(被回收)。当年轻代填满时,它会导致一个较小的集合,其中只收集年轻代;其他代中的垃圾不能回收。

绝大多数对象被分配到一个专门用于年轻对象(年轻代)的池中,大多数对象死在那里(被回收)。当年轻代填满时,它会产生一个小集合,其中只收集年轻一代;其他代的垃圾不会被回收。这种收集的成本与所收集的活动对象的数量成正比;充满死亡对象的年轻代很快被收集起来。通常,在每一次小规模的收集过程中,年轻代幸存下来的一些对象会被转移到老年代。最后,老年代会填满并且必须被收集,从而产生一个主要的集合,其中收集了整个堆。主要集合的持续时间通常比次要集合长得多,因为涉及的对象数量要大得多。下图展示了串行垃圾收集器中的默认代排列:

Description of Figure 3-2 follows

在启动时,Java HotSpot VM会将整个儿的Java堆保留在地址空间中,但是除非有需要,否则不会为它分配任何物理内存。覆盖Java堆的整个地址空间在逻辑上分为年轻代和老年代。预留给对象内存的完整地址空间可以分为年轻代和老年代。

年轻代由伊甸园(eden)和两个幸存空间(survivor spaces)组成。大多数对象最初是被分配到伊甸园(eden)中。一个幸存空间(survivor spaces)在任何时候都是空的,并且在垃圾收集期间为伊甸园(eden)中活动对象和另一个幸存空间(survivor spaces)提供服务;垃圾收集后,伊甸园(eden)和源幸存者空间是空的。在下一次垃圾收集中,两个幸存空间(survivor spaces)的目的是被交换。最近填充满的一个幸存空间是复制到另一个幸存空间的活动对象的来源。对象以这种方式在幸存空间之间复制,直到它们被复制了一定次数或者没有足够的空间。对象以这种方式在幸存者空间之间复制,直到它们被复制了一定次数,或者没有足够的空间。这些对象将被复制到老年代(old)区域。这个过程也被称为老化。

三.性能注意事项

垃圾收集的主要度量指标是吞吐量和延迟。
                  吞吐量是长时间内未花费在垃圾收集上的时间和总时间百分比。吞吐量包括分配中花费的时间(但通常不需要对分配速度进行调优)。
                   延迟是应用程序的响应能力。垃圾收集过程中暂停会影响应用程序的响应能力。

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

有些用户对其他考虑因素很敏感。内存占用是进程的工作集,以页面和高速缓存行来度量。在物理内存有限或进程较多的系统上,内存占用可能决定可伸缩性。及时性是对象死亡到内存可用之间的时间,这是分布式系统(包括远程方法调用(Remote Method Invocation, RMI))的一个重要考虑因素。

一般来说,为特定的代确定大小是这些考虑因素之间的权衡。例如,一个非常大的年轻代可能会最大化吞吐量,但这是以占用空间、及时性和暂停时间为代价的。使用小的年轻代以牺牲吞吐量为代价,可以最小化年轻代暂停。一个代的大小不影响另一个代的收集频率和暂停时间。

没有一种正确的方法可以决定"代"的大小。 最佳确定方式取决于应用程序使用内存的方式以及用户要求。因此,虚拟机对垃圾收集器的选择并不总是最优的,可以用命令行选项覆盖;参见影响垃圾收集性能的因素(下一章)

四.吞吐量和占用空间测量

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

例如,可以使用客户端负载生成器(client load generator)测试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的原因。 在此之后,将记录有关内存消耗的一些信息。该日志使用的格式是“在GC之前使用情况”—>“在GC之后使用情况”(“堆大小”)。

在示例的第一行中,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:gc*记录的G1 young回收的例子:

[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

 

 

猜你喜欢

转载自blog.csdn.net/weixin_37519752/article/details/89393357
今日推荐