(1) 案例问题

  • 很多时候一个系统开发完后,因为开发人员不熟悉 JVM 的运行原理和性能优化,部署生产环境的时候不会对 JVM 进行什么参数设置,可能很多时候就是用一些默认的 JVM 参数;
  • 默认的 JVM 参数往往是系统负载逐渐增高场景下的一个最大隐患;
    • 启动系统,默认可能就给你几百MB的堆内存,新生代、老年代可能都是几百MB的样子;
    • 前期没什么问题,到中后期,用户量逐渐增加,有一定负载后,就会出现一些问题;(高并发,Eden迅速填满,触发YoungGC,老年代对象新增迅速,频繁 Full GC,每小时都会触发好几次)
  • 我们期望,Full GC 一般正常情况下,都是以天为单位,比如每天发生一次,或几天发生一次 Full GC;

(2) 优化思路

  • 定制 JVM 参数模板(以运行在4核8G机器上为例)

    • 8G机器分一半内存给 JVM 堆内存(4G),可能其他的进程还需要使用内存;
    • 年轻代尽可能大一点,给3G,进而让每个 Survivor 都达到300MB,这样在 YoungGC 后;存活对象不会因为放不了 Survivor 或触发动态对象年龄判定规则,而进入老年代;
    • 加入 Compaction 参数,保证每次 Full GC 后会执行一次压缩,解决内存碎片的问题;
      1. -Xms4096M -Xmx4096M -Xmn3072M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0
  • 使用两个参数优化 Full GC 性能

    • -XX:+CMSParallelInitialMarkEnabled
      • CMS 垃圾回收器会在“初始标记”阶段开启多线程并发执行垃圾回收,减少 Stop the World 时间;
    • -XX:+CMSScavengeBeforeRemark
      • CMS 垃圾回收器会在“重新标记”阶段开始之前,先尽量执行一次 Young GC;
        • 先提前回收一些没人引用的对象,那么 CMS 在重新标记阶段就可以少扫描一些对象,减少 Stop the World 的时间;
  • 最终 JVM 模板参数
    -Xms4096M -Xmx4096M -Xmn3072M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSScavengeBeforeRemark
    -XX:+DisableExplicitGC -XX:+PrintGCDetails -Xloggc:gc.log 
    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/oom