1. 案例分析

有个同学负责的搜索服务经常半夜full gc,经过分析后,定位到具体原因,今天就分享出来

2. 系统介绍

类似今日头条app的搜索引擎。
这种搜索场景,在互联网公司非常普遍存在。
用户先输入关键字,
第一个搜索结果是综合,综合的意思就是所有的意思,包括了视频 咨询 百科等等。
第二个是视频,就是子搜索项
那么,讲到这里,就把简单的业务场景描述了一遍。
即互联网非常常见的搜索场景

3. 数据同步

那这个搜索场景和我们的jvm,有什么关系呢?
和jvm相关的,就是搜索ES index索引库初始化的问题。
用户在搜索之前,要先有数据把。
也就是说,如何把业务数据同步到搜索里面?
业务数据同步到搜索,一般有2种场景:

  1. 增量同步
  2. 全量同步

    增量同步

    image.png

    全量同步

    image.png
    好,介绍了这么多,终于进入我们的jvm了
    jvm的异常,就出现在全量同步的代码上。

    4. jvm的设置现状

    目前的搜索系统,设置了JVM为设置4G
    按照默认比例young : old = 1 : 2, eden : s0 : s1 = 8 : 1 : 1
    故如下图:
    image.png

    5. 这套系统的处理逻辑

    采用定时器,夜里1点钟
    由搜索服务,连接业务的mysql从库,每次读取2万条数据,
    然后把2万条数据写入到es的index库中。
    图s03
    image.png
    假设每条业务数据,约10个字段,平均每个字段100字节,大约1KB的数据
    每条线程从数据库读取2万条,约20MB,每次花费2秒钟
    使用了10条线程并行处理,
    10条线程 = 10 20万条,约10 20MB = 200MB,
    即, 2秒,处理20万条,200MB
    每秒处理数量= 20万/2秒 = 10万条
    10亿条耗费的时间 = 10亿 / 10万 = 10000秒 / 60秒 / 60分钟 = 2.7小时
    即2.7小时,把10亿条数据同步到ES索引库中
    故,理论上夜里1点钟执行,到4点钟就执行完了

    6. eden区多久被塞满

    eden区总共1.1g
    10条线程并发,2秒中产生200M对象数据
    故,10秒钟,填满1.1g eden区

    7. 每次ygc 有多少对象还存活?

    对象的存活,要先被栈帧引用,而栈帧存储在线程栈中。
    我们开了10条线程在并行,就有10个栈帧时刻运行着。
    也就是说,每时每刻有10条线程运行,约有200mb的数据被线程栈帧引用。
    所以,每10秒触发1此ygc时,就有200mb的数据存活。

    8. 如何处理存活的200MB数据?

    每次ygc后,200mb通过复制算法,复制给survivor区
    但是survivor被分割成from区和to区各占100MB
    from 100m 放不下200m,只能先把s_from的50m-100m(动态年龄50%)塞满后,剩下的100m-150m复制old区

9. old区多久被填满?

old区有2.7G
每10秒触发一次ygc,就有100m进old区。
我们来模拟这种动作:
第1次10秒,触发ygc,把eden的存活200M,其中100m复制进s_from,100m进old,最后清空eden
第2次10秒,触发ygc,把eden + s_from的存活200M,其中100m复制进s_to,100m进old,最后清空eden和s_from,并且from和to互换。
这里有2个问题?可能有的同学会问

  1. s_from第一次不是100m存活吗?怎么eden + s_from的存活才200M,不是300M吗

因为第一次的s_fromM经过10秒后,老早就没引用了,变为垃圾对象了,所以eden+s_from其实只有eden 200M存活
……
就这样经过了,2.7G / 100M = 27次ygc后,old区满了
27 * 10秒 = 270秒 / 60 = 4.5分钟
即,每4.5分钟后,触发full gc,这个速度还是比较高的,频繁的full gc会导致系统卡顿,影响性能。
image.png

10. jvm的性能分析

上文讲了这么多,我们要来优化jvm了,那该如何优化呢?
先明确一点,该案例有哪些问题?
该案例最大的问题就是经常full gc,导致系统卡顿。

那为什么会full gc呢
是因为每次ygc都有100mb,没地方放,导致进了old区!然后old区过了4.5分钟后触发full gc。
那现在要解决的问题就是100mb没地方放的问题,不要让他们进入old区。

那怎么解决呢?
把survivor_from 和 to区加到大于200M即可。
我们就把survivor设置为250MB吧

一:把堆再加1g,变成5g -Xms=5g -Xmx=5g
二:把年轻代设置为2g,-Xmn=2G
三:把survivor from to设置为256mb
-XX:SurvivorRatio=6;设置年轻代中Eden区和Survivor区的大小比值。
设置为6,则两个survivor区与一个Eden区的比值为2:6,一个survivor区占整个年轻代的1/8
2g的8分之一,就是256