赞
踩
原文:Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide--Generations。
Java SE平台的一个优点是它使开发人员免受内存分配和垃圾收集的复杂性的影响。然而,当垃圾收集是主要瓶颈时,理解这种隐藏实现的某些方面是有用的。垃圾收集器对应用程序使用对象的方式进行假设,这些假设反映在可调参数中,这些参数可以在不牺牲抽象功能的情况下进行调整,以提高性能。
当一个对象不能通过运行程序中的任何指针访问时,它就被认为是垃圾。最直接的垃圾收集算法就是迭代每个可达对象。剩下的任何对象都被认为是垃圾。这种方法所花费的时间与存活对象的数量成正比,这对于维护大量存活对象的大型应用程序来说是禁止的。
虚拟机包含许多不同的垃圾收集算法,这些算法使用分代收集组合在一起。缺乏经验的垃圾收集会检查堆中的每个活动对象,分代收集经验地利用大多数应用程序的观察到的几个特性,以最小化回收未使用(垃圾)对象所需的工作。这些观察到的特性中,最重要的是“弱代假说”,该假说认为大多数物体只能存活很短的一段时间。
图3-1中蓝色区域就是对象生命周期的经典分布。x轴是以分配的字节为单位度量的对象生存期。y轴上的字节数是具有相应生存期的对象中的总字节数。左边的尖峰部分表示可分配后不久就将被回收(换句话说,已经“死亡”)的对象。例如,迭代器对象只存活在单个循环期间。
有些对象寿命更长,所以其分布向右延伸。例如,一些通常在初始化时分配的对象一直存活到进程退出。在这两个极端之间是一些处于中间计算阶段的对象,在这里被看作是初始峰值右侧的部分区域。一些应用程序具有非常不同的外观分布,但令人惊讶的是,大量应用程序具有这种通用的形状。通过关注大多数对象“早逝”(生命周期短)这一事实(“弱代假说”此时成立),高效地回收成为可能。
为了优化这个场景,内存按代进行管理(存储不同年龄对象的内存池)。垃圾收集发生在每一代中,当这一代被填满时。绝大多数对象都分配在一个专门用于年轻对象(年轻代)的池中,并且大多数对象都死在这里。当年轻代填满时,它会导致一次"minor collection"(只回收年轻代,其他代中的垃圾不会被回收)。"minor collection"可以优化,假设'弱代假设'成立,并且年轻代中的大多数对象都是垃圾,可以回收。这类回收的成本与正在被回收对象的数量成正比;年轻代的死亡对象会被快速地回收。通常,在每次"minor collection"中,年轻代存活下来的部分对象会被提升到老年代;最终,老年代将被填满,必须(被)回收,触发"major collection"(整个堆都将被回收)。"major collection"通常持续的时间大于"minor collection",因为涉及的对象数量要多得多。
如上一节人机工程所述,人机工程学动态地选择垃圾收集器,以在各种应用程序上提供良好的性能。串行垃圾收集器是为具有小数据集的应用程序设计的,它的默认参数被选择为对大多数小应用程序有效。并行(或吞吐量)垃圾收集器用于具有中到大型数据集的应用程序。人机工程学选择的堆大小参数,加上自适应大小策略的特性,旨在为服务器应用程序提供良好的性能。
注意:如果垃圾收集成为瓶颈,您很可能不得不自定义总堆值的大小以及各个代的大小。检查详细的垃圾收集器输出,然后研究各个性能指标对垃圾收集器参数的敏感性。
图3-2 展示了默认的(内存)分代分布情况,除了并行收集器和G1:
在初始化时,最大地址空间实际上是有预留的,除非需要,否则不会分配给物理内存。保留给对象内存的完整地址空间将被划分到年轻代和老年代。
年轻代包括eden和两个survivor空间。大多数对象最初是分配到eden中的。一个survivor空间在任何时候都是空的,并作为eden中任何存活对象的目的地;另一个survivor空间是下一次复制回收期间的目的地。对象以这种方式在survivor空间之间复制,直到它们(年龄)足够老到可以终身保存(复制到老年代)。
Performance Considerations
垃圾回收性能主要有两种度量方法:
用户对垃圾回收有不同的要求。例如,有些人认为吞吐量对于web服务是一个正确的度量标准,因为垃圾收集期间的暂停也许是可以容忍的,或可以被网络延迟所掩盖。然而,在交互式图形程序中,即使是短暂的停顿也可能对用户体验产生负面影响。
有些用户对其他考虑因素很敏感。Footprint(内存占用)是一个进程的工作集,以page和cache line来度量。在物理内存有限或进程较多的系统上,FootPrint(内存占用)可能决定可伸缩性。Promptness(及时性)是从对象死亡到其内存可用时的一段时间,这是分布式系统(包括远程方法调用RMI)的一个重要考虑因素。
通常来讲,为特定的代选择大小是这些考虑因素之间的权衡。例如,一个非常大的年轻代可能会最大化吞吐量,但这是以Footprint(内存占用空间)、Promptness(及时性)和GC停顿为代价的。一个小的年轻代可以最小化年轻代的GC停顿时间,但这牺牲了吞吐量。一个代的大小不会影响另一代的回收频率和暂停时间。
没有一种合适的方法去选择一个代的大小。最佳的选择就是由应用程序使用内存的方式以及用户需求共同决定。因此,虚拟机对垃圾收集器的选择并不总是最优的,也可能会被Sizing the Generation中的命令行选项覆盖。
Measurement
吞吐量和内存占用空间最好使用特定于应用程序的指标来度量。例如,可以使用客户机负载生成器测试web服务器的吞吐量,而可以使用'pmap'命令在Solaris操作系统上测量服务器的内存占用。但是,通过检查虚拟机本身的诊断输出,可以很容易地估计由于垃圾回收而导致的暂停。
命令行选项'-verbose:gc'会在每次GC时打印关于堆和垃圾收集的信息。例如,下面是一个大型服务器应用程序的输出:
[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]
输出显示了两次'minor collection'和一次'major collection'。箭头之前和之后的数字(例如,第一行中的325407K->83000K)分别表示垃圾回收前后存活对象的大小。在'minor collection'之后,该大小包含了一些垃圾对象(不再存活),但不能回收。这些对象要么包含在老年代,要么被老年代引用。
括号中的数字(例如,第一行中的(776768K))是堆的提交大小:在不向操作系统请求更多内存的情况下,Java对象可用的(总)空间量。注意,这个数字只包含了survivor空间的其中一个。除了在垃圾回收期间,在任何给定的时间内都只使用一个survivor空间来存储对象。
行上的最后一项(例如,0.2300771秒)表示执行GC所花费的时间,在本例中大约是四分之一秒。
与第三行的'major collection'格式相似;注意:"-verbose:gc"生成的输出格式可能在将来的版本中发生更改。
命令行选项'-XX:+PrintGCDetails'会打印关于回收的一些额外的信息。下面有一个一个串行垃圾收集器使用'-XX:+PrintGCDetails'输出信息的示例:
[GC [DefNew: 64575K->959K(64576K), 0.0457646 secs] 196016K->133633K(261184K), 0.0459067 secs]
这表明'major collection'恢复了约98%的年轻代,DefNew: 64575K->959K(64576K),耗时0.0457646秒(约45毫秒)。
整个堆的使用减少到51%左右(196016K->133633K(261184K)),并且在最后0.0459067秒的时间表示,回收(在年轻代的回收之上)还有一些额外的开销。
注意:由'-XX:+PrintGCDetails'生成的输出格式可能在将来的版本中更改。
选项'-XX:+PrintGCTimeStamp'在每次回收的开始处添加一个时间戳。这有助于了解垃圾回收发生的频率。
111.042: [GC 111.042: [DefNew: 8128K->8128K(8128K), 0.0000505 secs]111.042: [Tenured: 18154K->2311K(24576K), 0.1290354 secs] 26282K->2311K(32704K), 0.1293306 secs]
收集在应用程序执行大约111秒后开始。"minor collection"大约在同一时间开始。此外,还显示了由老年代描述的"major collection"信息。老年代的使用减少到约10% (18154K->2311K(24576K)),并花费0.1290354秒(约130毫秒)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。