线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队

发布时间:2023-05-06 09:47:02

  1.1.1.1发现和调查问题 找出问题的原因

  问题的原因是我们收到了jdos容器CPU的报警,CPU的利用率已经达到了104%

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_堆内存

  观察机器日志发现,此时有许多线程在执行运行批准任务。通常,运行批准任务是低CPU和高内存类型,因此考虑到FulllGC引起的大量CPU占用(以前也有类似情况,通知用户重启应用程序后解决问题)。

  通过泰山查看机器内存的使用情况:

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_数据_02

  可见CPU利用率确实偏高,**但内存利用率并不高,只有62%,**属于正常范围。

  其实这里有点迷茫。按道理来说,这个时候内存应该已经满了。

  后来,根据其他指标,如突然进入流量,也怀疑jsf界面是由突然大量调用引起的cpu占据的,因此内存利用率不高,但后来慢慢排除。事实上,这里有点无助。这种现象与猜测不一致。只有CPU增长而没有内存增长。**单方面CPU增长的原因是什么?**然后,在这个方向调查了很长一段时间后,它被否认了。

  突然意识到监控会有“问题”吗?

  换句话说,我们看到的监控应该有问题。这里的监控是机器监控,而不是JVM监控!

  JVM使用的CPU可以反映在机器上,而JVM的高堆内存在机器上并不明显。

  然后去sgm查看相应节点的jvm相关信息:

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_字段_03

  可以看出,我们的堆内存在老年时代确实被打满然后清理干净,此时的CPU使用也可以与GC时间相对应。

  此时可以确定是Fulll GC引起的问题。1.2 找FULL GC的原因

  首先,dump发出了gc前后堆内存快照,

  然后使用JPofiler进行内存分析。(JProfiler是一种堆内存分析工具,可以直接连接在线jvm实时查看相关信息,也可以分析dump的堆内存快照,分析某一时刻的堆内存。)

  首先,解压我们的dump文件,修改后缀名称.bin,然后打开。(我们可以使用行云上自带的dump小工具,也可以通过命令手动dump文件去机器)

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_字段_04

  首先,选择Biggesttttt首先选择 Objects,查看当时堆内存中最大的对象。

  从图中可以看出,四个List对象占据了近900MB的内存,我们刚刚看到最大的堆内存只有1.3GB,因此,再加上其他对象,很容易将老年人占满,导致fulll gc问题。

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_字段_05

  选择最大的对象之一作为我们想要查看的对象

  此时,我们已经可以定位到相应的大内存对象对应的位置:

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_字段_06

  事实上,到目前为止,我们已经能够大致定位问题。如果仍然不确定,我们可以查看具体的对象信息。方法如下:

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_字段_07

  我们可以看到我们的大List对象,其实里面有很多Map对象,每个Map对象都有很多键值对。

  Map中的相关属性信息也可以在这里看到。

  相关信息也可以直接在以下界面看到:

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_堆内存_08

  然后一路点击,可以看到相应的属性。

  到目前为止,我们已经在代码中找到了大对象的位置。二、解决问题2.1 在代码中找到大对象的位置和问题的根本原因

  首先,我们根据上述过程找到相应的位置和逻辑p>

  我们项目的逻辑是这样的: 首先,我们将分析用户上传的Excel样本,并将其加载到内存中作为List变量,即我们在上面看到的变量。一个20w的样本,此时字段数量为a,占用空间约100mb。 然后根据用户样本中的数据,在用户样本中添加一些额外的请求数据,并根据该数据请求的相关结果。此时,字段数量为a+n,占用空间约为200mb。 循环完成后,将200mb的数据存储在缓存中。 开始生成excel,从缓存中取出200mb数据,并根据之前记录的a字段将初始样本字段取出填充excel。

  用流程图表示:

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_数据_09

  结合一些具体调查问题的图片:

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_堆内存_10

  其中一个现象是,每个gc后的最小内存正在逐渐扩大,与上述步骤中的第二步相对应,内存正在逐渐扩大。

  结论:

  将用户上传的excel样本加载到内存中,并将其作为一个List>存储结构时,首先,以这种方式存储20mbexcel文件会膨胀并占用约120mb的堆内存。此步骤将占用大量堆内存,由于任务逻辑,大对象内存将存储在jvm中4-12小时,导致任务过多,jvm堆内存容易填充。

  这里列出了为什么使用Hashmap会导致内存膨胀,主要原因是存储空间效率低下:

  在Hashmap中计算一个Long对象的内存:在结构上,只有Key和Value存储的两个长整形数据是有效数据,共16字节(2×8字节)。这两个长整形数据包装成java.lang.在Long对象之后,MarkWord分别有8字节、Klass指针8字节,long值8字节存储数据(一个包装对象占24字节)。

  然后这两个Long对象组成Mapp.Entry之后,又增加了16字节的对象头(8字节MarkWord+8字节Klass指针=16字节),然后是8字节next字段和4字节inthash字段(8字节next指针+4字节hash字段+4字节填充=16字节)。最后,在Hashmap中引用了Entry的8字节,增加了两个长整形数字,实际消耗的内存是(Long(24byte)×2)+Entry(32byte)+HashMapRef(8byte)=88byte,空间效率是除以所有内存空间的有效数据,即16字节/88字节=18%。

  ——5.2对Java虚拟机的深入理解.6

  以下是刚上传的excel中dump的堆内存对象,占用的内存达到128mb,而上传的excel实际上只有17.11mb。

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_字段_11

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_堆内存_12

  空间效率17.1mb/128mb≈13.4%2.2 如何解决这个问题

  暂时不讨论上述过程是否合理,解决方案一般可分为两类,一类是根本原因,即不将对象放入jvm内存中,而是存储在缓存中,而不是内存中,大对象问题自然很容易解决。另一种是症状治疗,即缩小大内存对象,使其在日常使用场景中一般不会触发频繁的fulll gc问题。

  两种方法各有优缺点:2.2.1 激进治疗:不要将他存入内存

  解决逻辑也很简单。例如,在加载数据时,将样本加载数据逐个存储在redis缓存中,然后我们只需要知道样本中有多少数量,并根据数量的顺序从缓存中取出数据来解决这个问题。

  优点:这个问题可以从根本上解决,以后基本不存在。无论数据量有多大,只需添加相应的redis资源即可。

  缺点:首先会增加很多redis缓存空间的消耗。其次,考虑到显示,对于我们的项目来说,这里的代码是古老而晦涩的,需要大量的工作量和回归测试来改变。

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_字段_13

2.2.2 保守治疗:减少其数据量

  分析2.1上述流程,第三步是完全不必要的,先存储缓存再取出,占用额外的缓存空间。(猜测是历史问题,这里不再深入研究)。

  其次,在第二步中,多余的字段n在请求结束后无用,因此可以考虑在请求结束后删除无用字段。

  此时,还有两种解决方案,一种是只删除无用字段来减少其map的大小,然后将其作为参数传输给生成excel;另一种方法是要求直接删除map,然后在生成excel时重新读取用户上传的excel样本。

  优点:变化小,不需要太复杂的回归测试

  缺点:在极端大数据量的情况下,fulll仍然可能出现 gc的情况

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_堆内存_14

  具体实现方式不展开。

  其中一种实现方式 ///获取有用的字段String[] colEnNames = (String[]) colNameMap.get(Constant.BATCH_COL_EN_NAMES);List colList = Arrays.asList(colEnNames);///去除无用字段param.keySet().removeIf(key -> !colList.contains(key)); 三、拓展思维

  首先,本文中的监控图是在复制当时场景时人工制作的GC。

  在cpu利用率图中,我们可以观察到cpu利用率的上升时间确实与gc时间一致,但当时场景中没有104%的cpu利用率。

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_堆内存_15

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_字段_16

  事实上,直接原因相对简单,因为虽然系统出现了fulll gc,但并没有频繁出现。

  小范围低频fulll gc不会导致系统的cpu飙升,这也是我们看到的现象。

  那当时的场景是什么原因呢?

线上FullGC问题排查实践——手把手教你排查线上问题 | 京东云技术团队_堆内存_17

  正如我们上面提到的,堆内存中的大对象会随着任务的进行逐渐扩展,所以当我们有足够的任务和足够长的时间时,每次full都可能导致 GC后的可用空间越来越小。当可用空间小到一定程度时,每次fulll gc完成后,发现空间仍不足,会触发下一个gc,导致gc频繁发生最终结果,导致cpu频率飙升。四、问题排查总结 当我们遇到在线cpu利用率过高时,我们可以首先检查它是否是fulll gc引起的问题,注意jvm监控,或使用jstat相关命令。不要被机器内存监控误导。 如果确定是gc引起的问题,可以通过JProfiler直接在线jvm或使用dump保存堆快照进行离线分析。 首先,你可以找到最大的对象,通常是由大对象引起的fulll gc。还有一种情况,不像这样明显是四个大对象,也可能是十几个50mb比较平衡的对象,具体情况需要具体分析。 通过上述工具找到问题对象,找到堆栈对应的代码位置,通过代码分析找到问题的具体原因,通过其他现象推断猜测是否正确,然后找到问题的真正原因。 根据问题的原因来解决这个问题。

  当然,以上只是一个不太复杂的调查情况。不同的系统必须有不同的内存。我们应该分析具体的问题,从这个问题中我们可以学到的是如果我们调查和解决问题的想法。

上一篇 不会JVM调优怎么进互联网大厂
下一篇 原生JS实现淡入淡出效果(fadeIn/fadeOut/fadeTo)

文章素材均来源于网络,如有侵权,请联系管理员删除。

标签: Java教程Java基础Java编程技巧面试题Java面试题