翻译:Java GC 调优(三) GC实现

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

    但是,当垃圾收集成为主要瓶颈时,了解GC实现的某些方面是很有用的。垃圾收集器对应用程序使用对象的方式进行了假设,这些假设反映在可调参数中,可以通过调整这些可调参数来提高性能,同时又不牺牲抽象的强大功能。

分代垃圾收集

    当对象在运行的程序中不能从任何其他活动对象的引用访问它时,这个对象被GC认为是垃圾,它的内存可以被VM重用。

    理论上,最直接的垃圾收集方法就是在每次运行时对每个可访问对象进行迭代,全部迭代完成后,剩余的对象被认为是垃圾,这种方法花费的时间与活动对象的数量成正比,这对于需要维护大量活动数据的大型应用程序是禁止的。

    Java HotSpot VM合并了许多不同的垃圾收集算法,这些算法都使用了一种称为分代收集的技术。最原始的垃圾收集每次都会去检查堆中的每个活动对象,而分代收集则利用大多数应用程序的一些经验观察到的属性来最小化回收未使用(垃圾)对象所需的工作。在这些观测到的特性中,最重要的是弱世代(Weak Generation)假设,即大多数对象只能存活很短的一段时间。

    下图蓝色区域是对象生存期的典型分布。x轴表示分配的字节数,y轴表示对应生命周期的总的字节数。左边的尖峰表示可以被回收的对象(在分配内存不久后,就”死亡“)。例如,迭代器对象通常只在单个循环的持续时间内存活。

    

部分对象存活时间较长,所以分布向右延伸。例如,有些对象在初始化时分配,知道VM退出时才销毁。介于两者之间的是在中间计算过程中存活的对象,可以参考在初始峰值右侧的块。部分应用程序具有与此非常不同的分布,但是,大多数程序具有这种一般形式,通过关注对象”早死“这一事实,可以实现高效的收集。

世代

    为了优化上述一般场景,内存是通过分代的方式进行管理的(不同内存池存放不同存活时间的对象),一旦分代满,就在该分代上执行垃圾回收。

    绝大多数对象被分配在专用于年轻对象(年轻代)的内存池中,大多数对象在年轻代死亡。当年轻代满时,在年轻代上会触发一个小的垃圾收集(Minor Collection)操作,其他代的垃圾不会被回收。这类垃圾收集的成本与所手机的存活对象的数量成正比,充满死亡对象的年轻代能够被快速回收。通常,在每次小收集期间,年轻代中幸存的部分对象会向老年代移动。最终,老年代被填满并且必须进行收集,这时会导致一个大的收集操作(Major Collection),整个堆都会被收集。主收集(Major Collection)通常要比次收集(Minor Collection)耗费更多的时间,因为涉及到的对象数量要多得多,下图展示了串行垃圾收集器中的内存分代情况:

    在启动时,JVM将整个Java堆保留在地址空间中,但除非需要,否则不会进行任何物理内存分配。Java堆上的整个地址空间从逻辑上被划分为新生代和老年代。年轻代由一个Eden和两个Survivor组成,大多数对象在Eden区域被初始化,其中一个Survivor在任何时候都为空,垃圾收集期间,Eden和另一个Survivor中的存活对象会被移动到空的Survivor中,垃圾收集结束后,Eden和另一个Survivor为空。在下一次垃圾收集时,两个Survivor空间的作用互相交换,存活对象被复制到另一个Survivor中。这种方式在Survivor中复制对象,当这些对象被复制了一定次数或者空间不足,这些对象被复制到老年代。这个过程也称为衰老(Aging)。

性能考虑

垃圾收集的主要度量指标是吞吐量和延迟:

    1.吞吐量是考虑程序在较长的时间周期内未用于垃圾收集的时间的百分比。吞吐量包括内存分配所花费的时间(但通常不需要优化内存分配速度)。

    2.延迟是应用程序的响应能力。垃圾收集暂停会影响应用程序的响应时间。

    用户对垃圾收集有不同的要求。例如,有人认为web服务主要指标为吞吐量,可以忍受垃圾收集期间引起的的程序暂停,或者说小的暂停可以看成被网络延迟掩盖。然而,在交互式图形程序中,即使是短暂的暂停也可能对用户体验产生负面影响。

    部分用户可能会有其他方面的考虑。内存时进程的工作集,以页面和缓存线为单位进行度量。在具有有限物理内存或多个处理器的系统上,内存占用可能决定程序的伸缩性。对象的死亡到该对象占用的空间可以再次分配的时间被称为及时性,在分布式系统中(包括远程方法调用(RMI)),及时性也是一个重要的考量因素。

    通常,选择特定的分代大小都是在这些因素之间进行权衡。例如,当年轻代非常大时,程序的吞吐量会大幅增加,但这样会牺牲内存占用、及时性以及掩藏暂停时间。年轻代的暂停时间可以通过减小年轻代空间牺牲部分吞吐量来缩短。代的空间大小不会对其他代的垃圾收集频率和暂停时间造成影响。

    选择分代的大小方法并不唯一。最佳选择往往取决于应用程序使用内存的方式以及用户的需求,因此,VM对垃圾收集器的选择并不总是最佳的,我们可以通过命令行手动覆盖默认选择。

吞吐量以及内存占用指标

    吞吐量和内存占用指标最好使用应用程序的特有指标来衡量。

    例如,web服务器的吞吐量可以使用客户机负载生成器进行测试,服务器的内存占用可以在Solaris操作系统上使用pmap命令进行测量。通过检查虚拟机本身的诊断日志,也容易估计垃圾收集导致的暂停。

    可以使用命令选项 -erbose: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标记号(GC(36)),本例中有3个GCs,分别为36、37、38,然后时记录gc的类型和gc的原因,在此之后,记录了有关内存消耗的一些信息,格式为 ”GC前堆内存->GC后堆内存(堆大小)“。

    示例第一行中,239M->57M(307M)表示GC前使用了239M内存,GC清理了大部分内存,但是保留了57M,堆大小为307M。在示例中,Full GC将堆从307M压缩到了了104M。在内存信息之后,GC的开始时间和结束时间以及持续时间被记录下来。

    注意,-verbose:gc命令时-Xlog:gc的别名。-Xlog是HotSpot JVM中的日志配置选项。-Xlog是一个基于标记的系统,其中gc是标记之一。如果需要获取更多的关于GC的操作信息,可以配置日志记录以打印任何具有GC标记和其他标记的消息。命令行选项为:-Xlog:gc*。

    下面是使用-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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值