JVM性能调整 - GC调整策略

(鉴于GC的复杂性,请务必先读本系列中的前几篇 Blog, 否则将会云里雾里!)

调整目标
  随着 Java 的演化,当初 C vs. Java 大战渐趋缓和:
    Java:     老兄! malloc / free 就是一名艺术?
    C:         好钢用到刀刃上,总感觉 GC 忙个不停一直占着资源在清理“垃圾”
    …


梦幻 GC 应该是这样子:
    1.    低开销 (low overhead)
    2.    低停顿时间 (low pause time)
    3.    有效的空间利用率 (space efficient)

但通常,我不得不从以上中来个”三选二”,因为不同的程序对GC有不同的需求:
    1.    吞吐量 (Throughput) - 花费在非GC的工作时间,也就是说GC时间是反吞吐量的 (这就是上述 C 老兄嘲笑的原因) 
        - -XX:GCTimeRatio =ratio (粗调,只是个 hint,能否达到取决于系统状态)
        - GC 时间对应用时间的比率: 1 / (1 + n) 
        - 例如: -XX:GCTimeRatio=19 设定 5% (1/(1+19)) GC 时间; -XX:GCTimeRatio=99 设定 1% (1/(1+99)) GC 时间
    2.    开销 (Overhead) - 即反吞吐量,即 GC 所占的时间
    3.    停顿时间 (Pause) - 由于GC 正在运行,程序看起来没响应的时长
        - -XX:MaxGCPauseMillis=n (粗调,只是个 hint,能否达到取决于系统状态)
        - GC 将尽最大努力调整相应参数来满足这一要求,也许会以降低吞吐量为代价
    4.    GC频率 (GC Frequency) - 相对应用执行时间GC执行的频率
    5.    空间利用率 (Footprint, 这个单词找不到准确的中文) - 完成GC功能需要的空间。 因为硬件的 cache line, memory, page 总是稀缺的,GC占去越多意味着真正用来干活的就越少
    6.    敏捷性 (Promptness) – 当对象变成垃圾了,多长时间它才被清理掉。也就是多长时间一个垃圾对象所占的内存被收回。包括分配式垃圾收集(Distributed Garbage Collection -DGC),如在RMI 场景

 


调整策略
1. 对所有GC适用
    1.    来个超大号的总没错!

        - 但是,最大值 <= 1/2 系统内存(除非你跑着玩,因为另一半留给操作系统其它看家进程和 mmap)
        - 且JVM能访问到的最大值因操作系统而异 (java -Xmx)
    2.    平衡
        - 堆空间,通常是越大越好,无论对 young 还是对 Old Generation 来说
        - 大的空间意味着GC不那么频繁,因此留有时间给对象成为“垃圾”
        - 对于 Young Generation  因为频繁的GC,本该成为垃圾的对象,会被过早地搬到 Survivor Space,进到那里的对象,如果存活,会占去来回在 S0 <-> S1 中拷贝的消耗。
        - 对于 Old Generation  我们希望,在一次GC过程中尽量多清除些垃圾,但频繁的GC活动,有时会显得过早(因为,也许过不了多久,又有一大批对象消亡)
        - 但小的空间或许意味着GC每一趟可以更快地结束 (但并不总是这样,如CMS)
    3.    Young Generation 大小
    因为 minor GC 频繁地运行,minor GC 算法的目标是快速。因此参数的选择不要违背这一宗旨

        - Young 整体空间大小调整
            - -XX: newSize=<n> : 初始 young generation 大小
            - -XX:MaxNewSize=<n> : 最大 young generation 大小
            - -Xmn n 更可取,因为它综合了 -XX:NewSize=<n> 和 -XX:MaxNewSize=<n>
            - 如果使用上述任一种方案指定 young generation 大小,那么 -XX:NewRatio=<ratio > 不再需要: young generation 与 old generation 的比率,如 ratio =3 表示 young/old = 1/3
            - CPU 核心增加了,适量增加 young generation 大小以充分利用并行,如果 heap size 固定了,那么增加 young generation 则意味着 old generation 减少,确保 old generation 足够大以容纳任何时候所有 live 对象 + 20% 空闲
        - Eden空间调整
            - 该大小意味着 minor GC 的频率,越小越容易填满,因此频率越高(不利于对象成为垃圾)
            - 该大小还意味着有多少比例的对象可以被夭折在伊甸园中(age=0),而不必花力气将他们搬到Survivor Space (age >0),进而送往 Old Generation,最后在 Major collection 中耗费力气 
            - 增加 Eden 大小并不总是减小 minor collection 的时间,因为 minor collection 时间花费在 拷贝Eden 中的 age=0的对象到 survivor space,以及处理 survivor space 中的 age>0 的对象
            - -XX:SurvivorRatio=<ratio> (见下面)
        - Survivor 调整
            - -XX:SurvivorRatio=<ratio>  单个 Survivor Space 与 Eden 的比例,例如,ratio=6 表示 每个 Survivor / Eden = 1/6 (即,每个 Survivor 是整个 Young Generation 的 1/8,因为有两个 Survivor Space)
            - -XX:TargetSurvivorRatio=<percent> 在每个(因为有两个) Survivor Space 到达这一比率时,将被提升到 old generation 中
            - -XX:InitialTenuringThreshold=<threshold> 因为是自适应(adaptive)GC, 初始值,在被提升到 old generation 之前,对象可在 young generation 存活的次数
            - -XX:MaxTenuringThreshold=<threshold>   因为是自适应(adaptive)GC, 最大值,在被提升到 old generation 之前,对象可在 young generation 存活的次数
            - -XX:+AlwaysTenure 从不将对象保留在 Survivor Space, 即每次 minor GC 直接将对象送往 Old Generation。对于应用包括很多 mid/long time live 对象适用,因为 可以避免在 Survivor Space 中来回拷贝的开销 
            - 权衡
                - 对于 mid/long live time 对象的比例小的应用
                - 尽可能多地将对象呆在 survivor space, 因为这样可以将他们在 young generation 就被回收
                - 更少提升到 old generation
                - 低频繁 major GC
            - 对于mid/long live time 对象的比例大的应用
            - 尽量避免来回在 survivor space 中拷贝,因为他们最终是进到 old generation
            - 但是,很难预言对象的生命周期长还是短,一般来说,在 survivor space 中拷贝比无谓地提升到 old generation 还是要好点
            - -XX:+PrintTenuringDistribution 用于监视 survivor size 的行为分配,并给出合适的 survivor 空间大小建议 
    4.    Old Generation 大小
        - Major collection 最主要目标是更好的空间利用率,而不是快速
        - 尽可能减少 major collection 的频率
        - 最好能容纳应用“稳态”时的活对象总大小 (说起来容易,做起来难,能容易地预测 WEB 服务器的高峰与空闲状态负载的回归线么?能很容易地预测分布式存储系统的 read write 比么?)。并适用加上20% 空闲
        - -Xms == -Xmx
            - 当 -Xms != -Xmx,堆的扩张或收缩需要 Full GC (什么是 Full GC?在大多数情况下,其等同于 major GC, 但在 i-cms 增量并发标记清扫 GC 模式下,有些不一样。)
            - -Xmx<n> 最大堆大小 (young generation + old generation)
            - -Xms<n> 初始堆大小 (young generation + old generation)
            - -Xmn<n> young generation 大小
            - 因此,-XX:MinHeapFreeRatio=<minimum> -XX:MaxHeapFreeRatio=<maximum> 不再需要
        - -Xms != -Xmx 也许适用于应用在整个生命周期中稳态的活对象总大小为 -Xms 赋的大小,但也许会在极小的情况下峰值负载(如 WEB 服务器)或数据集(存储系统)将达到 -Xmx赋的值。但是,是以增长堆空间大小的开销为代价的(Full GC)。因为,此时,GC 别无选择,在 OOME 导致 crash 之前,尽最大努力做一次 Full GC是值得的。
    5.    Permanent Generation大小调整
        - -XX:PermSize == -XX:MaxPermSize, 同样,因为 permanent 堆扩张或收缩需要 Full GC 
        - -XX:PermSize=<n>  permanent generation 初始大小
        - -XX:MaxPermSize=<n>   permanent generation 最大值
        - 通常,我们不幸地很难拿捏这个值
    6.    禁止自适用策略

        - 采用固定大小策略,还意味着 -XX:YoungGenerationSizeIncrement=<m>  -XX:TenuredGenerationSizeIncrement=<n>  -XX:AdaptiveSizeDecrementScaleFactor=<r> 都不需要
    7.    尽可能多地在对象还在 Young Generation 时将其回收
        - 如果对象不是 long-live 对象,最好在 minor collection 时就将其回收。否则,
        - 转入 Old Generation,major collection 发生的频率低,会增加 Footprint
        - 越多的 old generation垃圾,是终导致 major collection 频率升高和GC时间更长
    8.    Footprint 不应该超过系统可用的物理内存
        - 如果 Swap 开启(对于服务器应用,并不是好主意),swap in/out 或许可能缓解一会儿,但是程序将遭受 内存与I/O 访问的时间的差异 + swap 时间
        - 如果 Swap 没开启,哈哈,OOME! 
        - 对于个别 GC, 即使 swap 开启,如果大部分时间花在 swap in/out 蚂蚁搬家的话,也一样 OOME
        - 通常,目前的操作系统都有 mmap 用于 I/O,所以,不要贪心地将所有系统物理内存让 JVM 独吞。这样的话,你将失去来自操作系统的,免费而超优化的缓存功能!


2. 对Parallel GC / Parallel Old GC
    1.    默认情况下,该GC 认为它承载的系统只有它一个JVM
    2.    XX:ParallelGCThreads=<n>  指定并行的线程数
        - 考虑系统是否有多个JVM
        - 系统的处理器核心个数
        - 处理器是否支持一个核心多个硬件线程 (比如:优秀的UltraSPARC T1/T2)
    3.    –XX:MaxGCPauseMillis=n 指定最大 pause time 
    4.    –XX:GCTimeRatio=n 指定GC 花费时间与总体时间的比率:1/(1+n) 
    5.    因为 Parallel GC 为当今主流硬件开发并采用 ergonomics,因此默认情况下它自动调整过了,除非这种默认遇到严重问题
    6.    首先按照前述调整 young generation
    7.    降低 major GC 的频率
    8.    用于 low-pause 环境
        - 通过最大化 heap 空间
        - 通过避免或最小化 Survivor promotion 来避免 Full GC 


3. 给 CMS
    1.    首先按照前述调整 young generation
        - 别错误地认为,因为 CMS 并发能力发生在 old generation,因此忽略 young generation 的调整
    2.    更加小心地避免 过早的Survivor Space promotion
        - CMS 因为采用 free lists 的原因,提升开销很大
        - 越频繁的 Survivor Space promotion 越有可能造成堆碎片(fragmentation )
    3.    只调整 minor GC 同样可以利用 CMS
        - CMS 只作为最后一道防线,在应用负载超过以往的最大值时
        - 将 Full GC 安排在非关键时间段以减少堆碎片(fragmentation )
    4.    无法避免堆碎片(fragmentation )
        - 最终导致找不到足够大的空间来完成分配请求,即使 free 空间总计大小大于分配请求大小
        - 分配器为了效率,采用近似策略,这近似值最终浪费不少空闲空间
        - 不同大小的“大对象”是元凶
    5.    -XX:ParallelCMSThreads=<n>  并行的 CMS 线程数
    6.    –XX:+CMSIncrementalMode  启用增量模式
    7.    –XX:+CMSIncrementalPacing 控制增量模式下,每次的工作量
    8.    卸载 permanent generation 中的类
        - 默认情况下,CMS 不卸载 permanent generation 中的类
        - -XX:+CMSClassUnloadingEnabled  -XX: +PermGenSweepingEnabled 激活卸载 
    9.    CMS 启动时机
        - 前面的算法描述部分介绍了CMS GC 需要提前启动以避免回退到 Serial Mark-Sweep-Compact 模式
        - 启动太早,将导致频繁的 GC 周期以及高并发的开销
        - 启动太晚,最终回退到Serial Mark-Sweep-Compact 模式 Full GC。没有利用到 CMS 的优势
        - -XX:+UseCMSInitiatingOccupancyOnly - 默认情况下,CMS 会自动统计来找到最佳启动时机(即 ergonomic 机制),但是为了采用下面的控制参数,需要使该ergonomic 机制失效
        - -XX:CMSInitiatingOccupancyFraction=<percent> - 当 old generation 占用率达到这一百分比时,启动 CMS GC
        - -XX:CMSInitiatingOccupancyFraction=<percent> - 由这个配置算出的结果(占有大小)应该比应用“稳态”下的 所有live 对象大小大得多,否则,CMS 将不停地发生。因为一达到这个百分比就会触发
        - -XX:CMSInitiatingPermOccupancyFraction=<percent>  当 permanent generation 占用率达到这一百分比时,启动 CMS GC (前提是 -XX:+CMSClassUnloadingEnabled 激活了)
        - 对于 mid/long live time 对象的比例小的应用,可以晚些启动,因为:
        - 很少 Survivor Space promotion发生
        - old generation 增长缓慢
        - 低频率地 major GC (即 CMS GC)
        - 对于 mid/long live time 对象的比例大的应用,相对早些启动,因为:
        - 很多 Survivor Space promotion发生
        - old generation 增长很快
        - 高频率地 major GC (即 CMS GC)
    10.    使 System.gc() 明确地使用 CMS
        - System.gc() 总是进行一次 Full GC, 即使没有必要,所以 -XX:+DisableExplicitGC 可以用来忽略所有的 System.gc() 调用
        - -XX:+ExplicitGCInvokesConcurrent / -XX:+ExplicitGCInvokesConcurrentAndUnloadClasses 指定 System.gc()采用 CMS 算法
        - 对于应用信赖于 Sofe, Weak, Phantom references / finalizers 时有用

 

(Serial GC 就不单独调整了,因为除非是 Embedded JAVA, 否则,它会慢慢地消失!)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值