美文网首页
JVM 调优 —— 新生代 Survivor 空间不足

JVM 调优 —— 新生代 Survivor 空间不足

作者: 不知名的蛋挞 | 来源:发表于2018-12-09 17:04 被阅读168次

    引用文章:https://blog.csdn.net/wenniuwuren/article/details/50890824

    JVM的年轻代

    HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。

    在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

    新生代典型问题

    先看一段 GC 日志:新生代使用 ParNew, 老年代使用 CMS

    {Heap before GC invocations=0 (full 0):
     par new generation   total 943744K, used 838912K [0x0000000757000000, 0x0000000797000000, 0x0000000797000000)
      eden space 838912K, 100% used [0x0000000757000000, 0x000000078a340000, 0x000000078a340000)
      from space 104832K,   0% used [0x000000078a340000, 0x000000078a340000, 0x00000007909a0000)
      to   space 104832K,   0% used [0x00000007909a0000, 0x00000007909a0000, 0x0000000797000000)
     concurrent mark-sweep generation total 1560576K, used 0K [0x0000000797000000, 0x00000007f6400000, 0x00000007f6400000)
     concurrent-mark-sweep perm gen total 159744K, used 38069K [0x00000007f6400000, 0x0000000800000000, 0x0000000800000000)
    2016-01-19T14:15:34.532+0800: 13.812: [GC2016-02-19T14:15:34.532+0800: 13.812: [ParNew
    Desired survivor size 53673984 bytes, new threshold 1 (max 6)
    - age   1:   55521392 bytes,   55521392 total
    : 838912K->54474K(943744K), 0.0914620 secs] 838912K->54474K(2504320K), 0.0916240 secs] [Times: user=0.67 sys=0.06, real=0.09 secs]
    Heap after GC invocations=1 (full 0):
     par new generation   total 943744K, used 54474K [0x0000000757000000, 0x0000000797000000, 0x0000000797000000)
      eden space 838912K,   0% used [0x0000000757000000, 0x0000000757000000, 0x000000078a340000)
      from space 104832K,  51% used [0x00000007909a0000, 0x0000000793ed2ae0, 0x0000000797000000)
      to   space 104832K,   0% used [0x000000078a340000, 0x000000078a340000, 0x00000007909a0000)
     concurrent mark-sweep generation total 1560576K, used 0K [0x0000000797000000, 0x00000007f6400000, 0x00000007f6400000)
     concurrent-mark-sweep perm gen total 159744K, used 38069K [0x00000007f6400000, 0x0000000800000000, 0x0000000800000000)
    }
    {Heap before GC invocations=1 (full 0):
     par new generation   total 943744K, used 893386K [0x0000000757000000, 0x0000000797000000, 0x0000000797000000)
      eden space 838912K, 100% used [0x0000000757000000, 0x000000078a340000, 0x000000078a340000)
      from space 104832K,  51% used [0x00000007909a0000, 0x0000000793ed2ae0, 0x0000000797000000)
      to   space 104832K,   0% used [0x000000078a340000, 0x000000078a340000, 0x00000007909a0000)
     concurrent mark-sweep generation total 1560576K, used 0K [0x0000000797000000, 0x00000007f6400000, 0x00000007f6400000)
     concurrent-mark-sweep perm gen total 159744K, used 53249K [0x00000007f6400000, 0x0000000800000000, 0x0000000800000000)
    2016-01-19T14:15:41.943+0800: 21.222: [GC2016-02-19T14:15:41.943+0800: 21.223: [ParNew
    Desired survivor size 53673984 bytes, new threshold 1 (max 6)
    - age   1:  107256200 bytes,  107256200 total
    : 893386K->104832K(943744K), 1.2389070 secs] 893386K->210614K(2504320K), 1.2391870 secs] [Times: user=2.89 sys=0.35, real=1.24 secs]
    Heap after GC invocations=2 (full 0):
     par new generation   total 943744K, used 104832K [0x0000000757000000, 0x0000000797000000, 0x0000000797000000)
      eden space 838912K,   0% used [0x0000000757000000, 0x0000000757000000, 0x000000078a340000)
      from space 104832K, 100% used [0x000000078a340000, 0x00000007909a0000, 0x00000007909a0000)
      to   space 104832K,   0% used [0x00000007909a0000, 0x00000007909a0000, 0x0000000797000000)
      concurrent mark-sweep generation total 1560576K, used 105782K [0x0000000797000000, 0x00000007f6400000, 0x00000007f6400000)
     concurrent-mark-sweep perm gen total 159744K, used 53249K [0x00000007f6400000, 0x0000000800000000, 0x0000000800000000)
    }
    

    可以明显看出上述 GC 日志包含两次 Minor GC。 注意到第二次 Minor GC 的情况, 日志打出 "Desired survivor size 53673984 bytes", 但是却存活了 "- age 1: 107256200 bytes, 107256200 total" 这么多。 可以看出明显的新生代的 Survivor 空间不足。正因为 Survivor 空间不足, 那么从 Eden 存活下来的和原来在 Survivor 空间中不够老的对象占满 Survivor 后, 就会提升到老年代, 可以看到这一轮 Minor GC 后老年代由原来的 0K 占用变成了 105782K 占用, 这属于一个典型的 JVM 内存问题, 称为 "premature promotion"(过早提升)。

    "premature promotion” 在短期看来不会有问题, 但是经常性的 "premature promotion”, 最总会导致大量短期对象被提升到老年代, 最终导致老年代空间不足, 引发另一个 JVM 内存问题 “promotion failure”(提升失败: 即老年代空间不足以容乃 Minor GC 中提升上来的对象)。 “promotion failure” 发生就会让 JVM 进行一次 CMS 垃圾收集进而腾出空间接受新生代提升上来的对象, CMS 垃圾收集时间比 Minor GC 长, 导致吞吐量下降、 时延上升, 将对用户体验造成影响。

    新生代调优建议

    对于上述的新生代问题, 如果服务器内存足够用, 建议是直接增大新生代空间(如 -Xmn)。如果内存不够用, 则增加 Survivor 空间, 减少 Eden 空间, 但是注意减少 Eden 空间会增加 Minor GC 频率, 要考虑到应用对延迟和吞吐量的指标最终是否符合。

    要增大多少 Survivor 空间? 需要观察多次 Minor GC 过程, 看 Minor GC 后存活下来的对象大小, 最终确定 Survivor 的合适大小。 整个调优过程可能需要几次调整, 才能找到比较合适的值。调整几次后, 如果内存还是不够用, 就要需要考虑增大服务器内存, 或者把负载分担到更多的 JVM 实例上。

    Survivor 空间计算公式: survivor 空间大小 = -Xmn[value] / (-XX:SurvivorRatio=<ratio> + 2)

    相关文章

      网友评论

          本文标题:JVM 调优 —— 新生代 Survivor 空间不足

          本文链接:https://www.haomeiwen.com/subject/ajkkhqtx.html