这里写自定义目录标题
问题描述:产生如下异常
java.lang.OutOfMemoryError: GC overhead limit exceeded
Sun 官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。
【现象】如下图所示
打开Visual GC 查看JVM各个内存区的占用情况
Eden区的GC频率非常高,导致两个S区切换频率也高,Old区已经占满并且频繁GC,GC时间还特别长。
监视中可以看到,整体内存的使用量是高频率波动上涨趋势。
问题排查步骤:
1、 查看抽样器,看到热点方法是netty执行的一个select(),占用内存最大(当时处理时没截图),第二、第三分别是char[],byte[]。
2、 将网关中netty相关的代码屏蔽之后再执行,现象没有改善,排除网关的netty。之后查看引用了netty的依赖,发现框架组件component-lock中依赖的redisson有对netty的引用,其版本可能与网关的netty有冲突。
3、 屏蔽component-lock中redisson对netty的依赖后部署执行,开发环境、测试环境、UAT环境恢复稳定。于是更新到生产环境,却发现生产环境的监控图标趋势并不平稳,最终发展到如上图所示,发生OOM异常。
4、 将问题复现到开发环境。网关组件的启动参数设置为
-Xmx128m -Xms128m -Xmn32m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70
通过jmap命令将堆信息导出进行分析
jmap -dump:format=b,file=heap.bin 进程号
然后用MemoryAnalyzer打开heap.bin文件进行分析,发现手动编写的解码器产生了大量HashMap占用很大内存空间,于是升级为netty自带的解码器,问题已经解决了大半,Full GC的频率有所降低,但时间久了还是发生上面的内存溢出。继续分析。
通过top命令确定cpu占用最高的进程(排除JVM进程)
找出cpu占用最高的线程号:top -Hp 进程号 -d 1 -n 1
打印堆栈信息:jstack 线程号 > javadump.txt
把线程id 转换为十六进制:命令:echo “obase=16;线程号” |bc
在javadump.txt文件中查找得到的16进制字符串,定位到对应的堆栈
在本事例中,堆栈为
"nioEventLoopGroup-6-8" #124 prio=10 os_prio=0 tid=0x00007f4a801ec800 nid=0x68eb waiting for monitor entry [0x00007f4a7c0c6000]
java.lang.Thread.State: BLOCKED (on object monitor)
at javax.crypto.JceSecurity.getVerificationResult(JceSecurity.java:171)
- waiting to lock <0x00000000fb2c1210> (a java.lang.Class for javax.crypto.JceSecurity)
at javax.crypto.Cipher.getInstance(Cipher.java:653)
at com.eg.egsc.scp.smartchargegateway.util.RSAUtils.decryptByPrivateKey2(RSAUtils.java:194)
以下省略
定位到是项目代码中RSA解密方法导致的内存溢出。原来的代码如下:
Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
修改后的代码如下,将BouncyCastleProvider类声明为静态变量bcp
Cipher.getInstance("RSA", bcp);