最近笔者有点忙,这次OOM事故发生过去两周前,记得笔者那天正带着家人在外地玩,正中午跟友人吃饭的时候,钉钉连续告警爆表,接着就是钉钉电话(显示广东抬头)一看就知道BBQ了,又一次故障发生了,今天把那次故障复盘一下,做个总结,也给小伙伴分享一下 我是怎么从接到告警开始,怎么一步一步分析故障,然后定位到问题,最后完美解决,成功上线解决问题的。

上述告警内容,由于笔者所在服务是用CMS垃圾回收器,当其GC次数太频繁,达到公司监控平台设置的阈值时,就会通过钉钉通知告知开发者,发送到对应的控制台上。这个异常先从字面意义上来说倒也比较明显,如果老年代里的对象太多,无法提供空间容纳年轻代传递过来的对象的时候,就会触发FULL GC。

这里我们先简单分析一下,对象什么情况下会进入老年代,以及老年代又是在什么情况下会触发FULL GC?只有先知道了原理性东西,你才能带着思路去分析,真实线上场景属于对应哪种情况

首先科普一下对象什么情况下会进入老年代?

1)躲过15次GC之后进入老年代

public class Kafka{
//只要Kafka这个类存在,r这个静态变量就会一直存在
private static ReplicManager r=new ReplicManager();
}
像上面这块代码,成员变量是GCROOT引用,所以一直不会回收不掉;这个对象每次从Eden躲过一次到Survivor区域中,它的年龄就增长1岁,当年龄增加到15岁时候,就会转移到老年代里。
 
2)动态对象年龄判断
意思是如果Survivor空间中相同年龄的所有对象大小总和大于Survivor空间的一半,年龄大于等于该年龄的对象会直接进入老年代
 
3)大对象直接进入老年代
 
4)空间担保策略
在发生MinorGc之前,JVM会检查老年代的最大连续可用空间是否大于新生代所有对象总空间,如果不成立,那么JVM会查看一个参数值查看是否允许担保,如果之前配置了允许,那么会检查老年代最大可用空间是否大于历次晋升到老年代对象的平均大小,如果大于将尝试进行一次Minor GC;如果小于或者参数设置不允许冒险,那么就会进行一次FULL GC。
 
那老年代又是在什么情况下会触发FULL GC?
1.也是上面第四种情况,就不写了
2.yongGC之后如果满足上述分析的[#首先科普一下对象什么情况下会进入老年代?]其中一种情况,那么进入老年代,但这个时候如果老年代空间不足,就会触发FULLGC
3.如果老年代内存使用率超过92%其实会触发fullgc的
 
好了先科普一下相关知识点,利于后续的分析做铺垫。下面开始逐步分析具体具体原因,到底是什么大对象充满了老年代内存区域。
 
首先一碰到特别是线上这种重大事故,第一思路是保留线程,然后快速止血。这也是笔者所在公司对于开发的其中一条军规(估计之前出现过太多的这种事故,形成规范了)。
那我也按照这种思想,通过日志分析,马上知道这个是有同时在批量导数据,导致入口流量很大,先联系同时快速止血,暂停导入操作。果然没多久 就不报警了,告警恢复通知一个接一个过来。
 
接下来我们开始分析到底是什么对象快速把老年代给填满了,相应入口在哪里。
先看业务监控大图:

现象是从下午4点开始内存有一波快速增长。
通过阿里的Arthas分析工具,通过命令dashboard查看当下系统的实时信息。
(下面这张图已经是止血之后文档的图了,但老年代还是填充了不少对象的)

线上由于比较麻烦dump线程。而且现场已经过去了,所以我还是自己写了一段压测代码(类似Jmeter效果),来压测相应的总入口,看看具体是哪个对象占了大内存

很明显是有一个nashorn相关对象占据了比较大的占比。那这个对象其实对应笔者的程序是

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");
Compilable compEngine = (Compilable) engine;
try {
CompiledScript compile = compEngine.compile(script);
}catch(Exception e){

}

简单来说,Nashorn的编译入口可以从 Context.compileScript() 开始看:[ JavaScript源码 ] -> ( 语法分析器 Parser ) -> [ 抽象语法树(AST) ir ] -> ( 编译优化 Compiler ) -> [ 优化后的AST + Java Class文件(包含Java字节码) ] -> JVM加载和执行生成的字节码 -> [ 运行结果 ]

此过程是十分耗时的,每次执行eval 去运行js ,都需要编译成字节码、然后加载执行。同时会将编译过的字节码缓存起来,以便后续使用,因此加载的类会长时间存活,占用很大的内存空间。

所以笔者尝试将CompiledScript这一对象第一次编译完后,本地缓存起来用

private static Map<Long, CompiledScript> scriptMap = new ConcurrentHashMap<>();
缓存起来,下一次如果已经存在,就直接拿来用。
重新压测后效果还是明显的

总结
线上场景 特别对于一些新的框架或技术 如果你的流量很大,笔者那时参与了这个项目,工期特别短,功能又特别多,想着先上线,下一步再做压测,想不到等不到下一步问题就暴露出来了

 
 

公司内部一次关于OOM故障复盘分享的更多相关文章

  1. 公司内部一次关于kafka消息队列消费积压故障复盘分享

    背景现象 1.20晚上8点业务线开始切换LBS相关流量,在之后的1个小时时间内,积压量呈上升趋势,一路到达50W左右,第二天的图没贴出具体是50W数字,以下是第一天晚上的贴图部分. 现象一: 现象二: ...

  2. HBase 查询导致RegionServer OOM故障复盘

    背景:我司作为某运营商公司的技术咨询公司,发现有第三方开发公司在使用HBase 1.1.2 (HDP 2.4.2.258版本)一段时间使用正常后,从某一天开始报OOM,从而导致RegionServer ...

  3. Rafy 领域实体框架 - 公司内部培训视频

    本月给公司内部一个项目做架构重构,其中使用到了 Rafy 框架.所以我培训了 Rafy 领域实体框架的使用方法,过程中录制了视频,方便其他同事查看.现在把视频放到园里来分享下,有兴趣的朋友可以看看,有 ...

  4. 故障复盘究竟怎么做?美图SRE结合10年经验做了三大总结(附模板)

    美图崇尚的故障文化是 "拥抱故障,卓越运维",倡导的基准是 No-Blame, 即「不指责,重改进」.今年 9 月 TakinTalks 社区曾经分享过美图的三段式故障治理方法(美 ...

  5. 一次线上OOM故障排查经过

    转贴:http://my.oschina.net/flashsword/blog/205266 本文是一次线上OOM故障排查的经过,内容比较基础但是真实,主要是记录一下,没有OOM排查经验的同学也可以 ...

  6. 公司内部Samba 服务器架设

    1.需求 在公司内部打造一个文件管理系统,其作用域仅仅在公司内部,支持在线对文件的修改和保存操作等,同时也要注意权限问题. 2.策划 目前设立四个群组:运维.开发 .测试和普通,当然所对应的对文件的访 ...

  7. 关于运维之故障复盘篇-Case Study

    关于故障的事后复盘,英文名 Case Study是非常有必要做的,当然是根据故障的级别,不可能做到每个故障都Case Study,除非人员和时间充足: 文档能力也是能力的一种,一般工程师的文档能力比较 ...

  8. 生产环境想要对某个Pod排错、数据恢复、故障复盘有什么办法?

    生产环境想要对某个Pod排错.数据恢复.故障复盘有什么办法? k8s考点灵魂拷问9连击之5 考点之简单描述一下k8s副本集ReplicaSet有什么作用? 考点之为什么ReplicaSet将取代Rep ...

  9. 搭建公司内部的NuGet Server

    随着公司业务慢慢的拓展,项目便会越来越来多,很多项目会依赖其他项目DLL,比如一些底层的技术框架DLL引用,还有各业务系统的也有可能会有引用的可能. 项目多,交叉引用多,如果要是有一个DLL更新,那就 ...

随机推荐

  1. FFT通过傅里叶级数图解频域补零时域内插

    在时域频域的信号分析的过程中,一个常见的说法叫:频域数据补零会让时域数据内插. 意思是在频域数据中多补几个零,再做ifft(逆傅里叶变换)后的时域数据,会变得更加"细腻",分辨率会 ...

  2. 学习MyBatis必知必会(7)~注解开发、动态SQL

    一.MyBatis的注解开发 开发中推荐是使用xml文件配置 1.配置映射关系[使用注解的方式]: <!-- 全局的配置文件 --> <configuration> <! ...

  3. 为什么C语言Scanf函数对字符串不要加 取地址运算符&

    原文1:http://www.360doc.com/content/16/0515/11/19455598_559288667.shtml 原文2:https://zhidao.baidu.com/q ...

  4. jar包冲突时怎么办

    因为项目中会依赖许多jar包,免不得就会有冲突,那怎么解决呢? 使用 mvn dependency:tree 可以看到各个包的依赖关系 [INFO] | +- commons-cli:commons- ...

  5. NSString为何要用copy修饰,而不是strong?

    NSString本身是无所谓的,但是如果一个 NSString 指针指向了一个 NSMutableString的内存空间的话,如果使用 strong 修饰的话,如果你在别处修改这个值的话,那么原来的值 ...

  6. 【转】Mysql相关子查询&&MySQL获取分组后的TOP N记录

    https://www.cnblogs.com/Yiran583/p/6743870.html select * from test1 a where 2 > (select count(*) ...

  7. ANT中的copy和move标签

    Copy标签 该标签用于文件或文件集的拷贝,其属性如下: file 表示源文件. tofile 表示目标文件. todir 表示目标目录. overwrite 表示指定是否覆盖目标文件,默认值是不覆盖 ...

  8. Redis 源码简洁剖析 11 - 主 IO 线程及 Redis 6.0 多 IO 线程

    Redis 到底是不是单线程的程序? 多 IO 线程的初始化 IO 线程运行函数 IOThreadMain 如何推迟客户端「读」操作? 如何推迟客户端「写」操作? 如何把待「读」客户端分配给 IO 线 ...

  9. 帆软报表(finereport)鼠标悬停背景变色

    在报表中,为了突出鼠标所在单元格,当鼠标悬浮时突出背景色(字体),鼠标离开后恢复原有的背景色(字体). 在设计器 模板>模板 Web 属性>填报页面设置,去除填报当前编辑行背景设置的勾选, ...

  10. RISC-V 特权指令结构

    机器模式 机器模式(缩写为 M 模式,M-mode)是 RISC-V 中 hart(hardware thread,硬件线 程)可以执行的最高权限模式.在 M 模式下运行的 hart 对内存,I/O ...