Java 内存溢出排查

Java OOM 毫无疑问是开发人员常见并且及其痛恨的问题,但是任何服务的开发都没法避免 OOM。 因此,OOM 的排查及定位是每个 Java 工程师都必备的技能。

 

所遇到的问题

 

在使用 scala 开发的一个 web 服务,在用户使用中,经常出现: java.lang.OutOfMemoryError: Java heap space 。而且还束手无策,每次都只能重启服务解决。

 

准备

 

服务使用 jetty 发布的,先来看一下我这个服务的启动参数:

 

/opt/soft/jdk/jdk1.7.0_40/bin/java \

  -server -Xmx4G -XX:MaxPermSize=1024M -XX:PermSize=256M \

  -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:-CMSConcurrentMTEnabled -XX:CMSInitiatingOccupancyFraction=65 -XX:+CMSParallelRemarkEnabled \

  -XX:+HeapDumpOnOutOfMemoryError \

  -XX:HeapDumpPath=/opt/soft/heapdump/ \

  -Dscala.concurrent.context.numThreads=500 \

  -Dscala.concurrent.context.maxThreads=500 \

  -Dfile.encoding=UTF-8 -jar start.jar >> log 2>&1 &

 

排查

 

通过增加了参数 -XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath 当在 OOM 的时候,服务会生成一个 java_pid$pid.hprof 二进制文件。

 

下面就是使用工具分析这个 .hprof 文件来定位问题了。使用 Memory Analyzer (MAT) 来分析该文件,效果如下:


 

效果很吓人,什么鬼,什么东西,吃了 3.8G 的内存,我#%$#@#@#&^&^&#$….

打开 Leak Suspects» Leaks» Problem Suspect 1 看到如下详情:

 

 

一开始可能没那么快找到问题,但是这个图已经很明显说明了问题,是 ArrayList 的内容太大,沾满了内存。但是你可能还不清楚具体那块代码导致,这个时候你可以点击那个 ArrayList 在左侧栏看 Attribute。

 

然后一直鼠标右键 into 进去看里面的详情,最终是可以看内容的。

 

问题原因

 

问题排查到最后,看到的是 ArrayList 里面存的全是 ResponseBodyPart, 然后就想到了项目使用到 Dispatch 请求下载结果文件,

于是乎去找到问题代码,错误代码如下:

 

val outputReq = dispatch.url(url) / "task" / "output" / id

val outputFuture = Http(outputReq > { res =>

  val out = new FileOutputStream(outputFile(taskId), true)

  IOUtils.copy(res.getResponseBodyAsStream(), out)

  out.close

})

 

看不出问题,感觉一切正常。翻源码会发现,res.getResponseBodyAsStream() 之前,已经将所有内容都存入一个 ArrayList 当中了。哎,没用对啊。

 

解决办法

 

问题已经定位到,于是去了解了一下这个项目,该如何使用 stream 的方式来读取并写入文件流。然后发现,人家有一个 read line by line 的实现。但是切割上其实是有问题的,因为拿到一批 bytes 之后,直接转成了 string 并用分隔符分割,奈何内容里面有中文,出现乱码了。

 

最终,参考项目本身的 as.stream.Lines 写了一个 as.stream.Bytes 来通过 bytes 边读边写,如下:

 

val bos = new BufferedOutputStream(new FileOutputStream("/tmp/file.txt", true))

val outputFuture = Http(outputReq > as.stream.Bytes(bytes => {

  bos.write(bytes)

}))

 

总结

 

主要描述了分析问题的思路和方向,问题都大同小异,OOM 总会有原因的,有原因肯定可以找到并解决。MAT 这个分析工具很实用,内容很详细。以前遇到 OOM 问题都是重启服务,治标不治本,还是要多分析问题并解决。

Java内存溢出指的是在Java程序运行过程中,因为申请的内存超出了可用内存的限制,导致程序终止的现象。下面是一些排查Java内存溢出问题的方法: 1.确定是否是内存溢出问题:查看程序的错误日志或异常信息,如果有OutOfMemoryError的错误信息,可以确定是内存溢出问题。 2.分析问题发生的位置:追踪错误日志或异常堆栈,定位到代码中可能导致内存溢出的地方,比如不断创建对象、大量递归调用等。 3.查看程序的内存使用情况:可以使用Java的内存分析工具,如jmap、jstat等,查看程序运行时的内存使用情况,包括堆内存和非堆内存的使用情况。 4.检查代码中是否有资源未释放:Java中需要手动释放的资源包括文件流、数据库连接等,如果资源没有正确释放,会导致内存泄漏,最终导致内存溢出。 5.检查是否存在循环引用:循环引用指的是多个对象相互引用,导致垃圾回收器无法回收它们,最终导致内存溢出。可以使用内存分析工具来分析程序中是否存在循环引用的情况。 6.调整JVM参数:可以通过调整JVM的参数来增加可用内存,比如增加堆内存的大小。可以使用命令行参数'-Xms'和'-Xmx'来指定初始堆大小和最大堆大小。 7.优化代码:检查代码中是否存在不必要的对象创建、频繁的垃圾回收等问题,优化程序的设计和算法,减少内存使用。 8.升级JDK版本:某些JDK版本中可能存在内存泄漏或其他内存相关的问题,升级到最新的JDK版本可以解决一些内存溢出问题。 总之,排查Java内存溢出问题需要分析错误日志、查看内存使用情况、检查代码和资源释放等等,找出问题的根源并及时修复。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值