![a75a1a51ebcff7976732e11313e46220.png](https://i-blog.csdnimg.cn/blog_migrate/0fe4e70159fa09a2199761ac1a0dd7b4.jpeg)
目前疫情还在持续,在家休息也20多天了,很久没更新文章了。今天公司的微信预警上收到服务宕机的信息,远程连接发现服务进程还在但出现如下错误信息:
xxxx系统异常,异常信息:org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded
典型的内存溢出问题,分析这种问题我多种思路去解决这里我介绍下我排查这个问题的过程和大家分享下:
1.查看服务jvm内存使用情况
我们通过jmap命令实现,通过jmap命令我们可以很详细的查看内存使用情况汇总、及对内存溢出进行定位与分析。
1.1.定位服务进程
jps -l
1.2.根据进程分析内存使用情况
jmap -heap 31650
我们得到的内存分析结果如下:
Attaching to process ID 12112, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.162-b12using thread-local object allocation.Parallel GC with 2 thread(s)Heap Configuration: MinHeapFreeRatio = 0 MaxHeapFreeRatio = 100 MaxHeapSize = 536870912 (512.0MB) NewSize = 178782208 (170.5MB) MaxNewSize = 178782208 (170.5MB) OldSize = 358088704 (341.5MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB)Heap Usage:PS Young GenerationEden Space: capacity = 170393600 (162.5MB) used = 164111320 (156.50875091552734MB) free = 6282280 (5.991249084472656MB) 96.31307748647836% usedFrom Space: capacity = 524288 (0.5MB) used = 0 (0.0MB) free = 524288 (0.5MB) 0.0% usedTo Space: capacity = 4718592 (4.5MB) used = 0 (0.0MB) free = 4718592 (4.5MB) 0.0% usedPS Old Generation capacity = 358088704 (341.5MB) used = 357753512 (341.18033599853516MB) free = 335192 (0.31966400146484375MB) 99.90639414305568% used
通过分析我们看到Old Generation老年代的使用率达到99.9%,导致没有足够的内存进行GC操作,这就是导致程序内存溢出的原因。
那具体是哪里导致的呢?这里就要进行下一步的操作。
2.将内存使用情况dump到文件中
我们执行如下命令:
jmap -dump:format=b,file=/home/wxt/dump.dat 31650
我们将内存使用dump到我们指定目录下,dump完成后进行我们最重要的一步,内存分析。
3.通过heaphero分析内存
jvm的内存分析工具要很多像jhat,这里我介绍一款在线分析工具heaphero,网站地址:
https://heaphero.io/heap-index.jsp#header
![414e0e6668946c34feee98994f77eee3.png](https://i-blog.csdnimg.cn/blog_migrate/c1c3763cf56fb18f78bed5d33dd250ea.jpeg)
我们将dump下来的内存文件上传后可以得到如下的分析结果:
我们定位到Large objects部分:
![c9a83ee68e0a59ad8affde85839dbebe.png](https://i-blog.csdnimg.cn/blog_migrate/47e96801cac9ff5d4d7d17cda659293b.jpeg)
我们看到 Java Static javax.crypto.JceSecurity.verificationResults
<>这里内存的占用达到了89.3%,显然是很不正常的。我们进入连接后进一步分析发现:javax.crypto.JceSecurity.verificationResults的实例占用了最多的堆内存,主要是一个IdentityHashMap类型的属性verificationResults,放了很多org.bouncycastle.jce.provider.BouncyCastleProvider对象(达到251,062K )。
通过对源代码进行分析我们定位到了如下代码块:
private static byte[] encrypt(RSAPublicKey publicKey, byte[] plainTextData) throws Exception {if (publicKey == null) {throw new Exception("加密公钥为空, 请设置");}Cipher cipher = null;byte[] output = null;try {cipher = Cipher.getInstance("RSA", new BouncyCastleProvider());cipher.init(Cipher.ENCRYPT_MODE, publicKey);output = cipher.doFinal(plainTextData);return output;} catch (Exception e) {MySlf4j.textError("公钥加密过程异常:{0}", MySlf4j.ExceptionToString(e));throw e;}}
每次new BouncyCastleProvider 都会产生非常多的对象和引用(占用251,062K ),且缓存在JceSecurity的verificationResults没法释放。
找到问题后解决就很简单了,将BouncyCastleProvider实例对象作为Rsa的静态成员变量可以解决问题。
private static Provider bouncyCastleProvider=new BouncyCastle.......cipher = Cipher.getInstance("RSA", bouncyCastleProvider);