本文大纲

《JVM剖析及性能跟踪》

案例5--MemoryAnalyzer分析32G大堆内存

正文

引言

在Java应用程序的开发与运维过程中,内存溢出(OutOfMemoryError)是一个常见且棘手的问题。尤其是当应用程序需要处理大量数据时,堆内存的占用可能会迅速膨胀,导致内存溢出,进而引发进程崩溃。面对这种情况,如何有效地分析堆内存,找出内存泄漏或内存使用不当的根源,成为了解决问题的关键。本文将分享一个实际案例:如何在一台仅有16G内存的笔记本电脑上,使用MemoryAnalyzer(MAT)分析一个32G的巨大堆内存文件,并最终定位问题。


困难:分析32G的巨大堆内存

面对32G的巨大堆内存,分析工作充满了挑战。首先,我的笔记本电脑内存仅为16G,远小于堆内存的大小。常见的Java内存分析工具如JVisualVM,在面对如此巨大的堆内存文件时,直接报错无法进行分析。此时,**MemoryAnalyzer(MAT)**成为了解决问题的理想工具。MAT不仅能够处理大型堆内存文件,还提供了强大的分析功能,帮助开发者深入挖掘内存问题的根源。


背景:Solr搜索引擎集群的内存溢出问题

我们有一个由4个节点组成的Solr搜索引擎集群,运行一段时间后,频繁发生内存溢出,导致进程崩溃。尽管我们尝试了多种常规的调优手段,问题依然没有得到根本解决。具体尝试的优化措施包括:

  1. 索引分片与数据复制:将索引分为4个分片,数据复制2份,但集群仍然不稳定,内存溢出问题依旧。
  2. 全量数据单实例运行:尝试不进行分片,每个实例都存储全量数据,虽然稳定运行了一个月,但当索引数据达到5G后,内存溢出问题再次出现。
  3. 分片与复制结合:将索引分为2片,数据复制2份,共4个实例,1、2实例合起来存储全量数据,3、4实例作为备份,但问题仍未得到根本改善。
  4. 单实例大内存分配:尝试在单个实例上分配32G甚至64G内存,运行全量数据,但内存溢出问题依然存在。

经过多次尝试,问题依然没有得到解决,因此我们决定深入分析堆内存,找出内存溢出的根本原因。

临时方案

每天0点定时重启solr,使用 crontab -e 新增一个定时任务。 

#每天0点执行一次
0 0 * * * /bin/bash /usr/local/solrcloud/restart.sh

定位到内存

当快要发生内存溢出时, 使用top命令查看系统 ,如下图:

  • 系统负载高较,图中是负载是 12
  • 29262进程是solr单实例进程 ,当时分配了64G的JVM内存,它的CPU使用率是 155%
  • 此时的搜索引擎响应速度已变慢。

image2022-6-29_7-23-29.png

查看cpu占用高的线程

  • 命令  top -H -p 29262
  • 发现 线程ID是29434的线程,占用CPU达到99.7%

image2022-6-29_7-23-43.png

转换线程ID

  • printf “%x\n” 29262
  • 结果是  72fa

定位哪个线程cpu占用

  • jstack 29262 | grep 72fa -A 30
  • 下图可看出,占cpu100%的是GC线程。
  • 此时64G内存已不够使用,GC回收线程忙不过来了。可以定位是内存问题。

image2022-6-29_7-23-8.png

使用MemoryAnalyzer分析32G的巨大堆内存

上文的截图是64G JVM内存,但下面是32G JVM内存。因为虽是同一个solr集群, 但由于期间的反复折腾与尝试,所以不是同一段时间做的记录工作。

当做了这里时了,当时的solr集群使用的就是32G JVM内存。 所以这里针对 32G JVM内存进行分析。

1. 生成堆转储文件

为了分析内存问题,我们首先需要生成堆转储文件(Heap Dump)。使用以下命令生成堆转储文件:

  • jmap -dump:live,format=b,file=myjmapfile.hprof <pid>

    该命令会生成一个二进制格式的堆转储文件(myjmapfile.hprof),仅包含存活对象。

2. 导入并分析堆转储文件

将生成的32G堆转储文件导入MemoryAnalyzer进行分析。尽管我的笔记本电脑内存仅为16G(Intel I7 8代),MAT仍然能够处理如此巨大的文件。整个分析过程耗时约4个小时,最终MAT成功加载并分析了堆内存数据。

image2022-6-30_0-1-58.png

3. 分析结果

通过MAT的分析,我们发现32G的堆内存中,有28G被Thread对象占用,而不是正常的业务对象。这一异常现象表明,Solr的某个版本可能存在线程管理方面的Bug,导致线程对象无法被正确回收,从而引发内存溢出。

后续决定升sorl的版本试一试。

image2022-6-30_0-2-45.png

image2022-6-30_0-3-14.png

总结

本文演示了 使用我16G内在的笔记本电脑 + MemoryAnalyzer 分析32G的堆内存。

正明了,当分析大型堆内存时,MemoryAnalyzer是理想的工具。