java排查内存溢出

2 篇文章 0 订阅
1 篇文章 0 订阅

 

目录

1背景:

1.1:什么是内存溢出,为什么会内存溢出?

2排查问题:

Overview视图

histogram视图(重要)

Dominator Tree(重要)

3问题复现:

4解决问题:

5总结:


1背景:

最近公司线上项目出现了java.lang.OutOfMemoryError  ......java heap space.....记录总结一下。

1.1:什么是内存溢出,为什么会内存溢出?

         首先,内存溢出简单的理解就是内存不够用了,这里的内存不足就是指堆的内存不够用了,附一张JVM内存模型

那为什么会不够用了呢?

原因:Java虚拟机内存模型中堆用于存储对象实例,当GC Roots到创建的对象之间有可达路径时,就可以避免垃圾回收机制回收这些对象,那么只要不断地创建这样的对象,当数量到堆的容量限制后就会产生堆内存溢出异常(OOM for Heap)

 

2排查问题:

  • 等待阶段:由于出现错误时项目还没有接入监控,所以我们不能第一时间发现错误,无法第一时间用jmap -dump:format=b,file=heap.bin dump出内存快照文件,于是我们在启动参数上添加了-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/heap/dump参数来发生oom时能dump出内存快照文件,以便后续我们用MAT分析。

 

  • 着手排查:从运维拿到快照文件之后我们就开始用MAT开始分析,下图是MAT分析完系统堆内存的使用情况:

    Overview视图

    该视图会首页总结出当前这个Heap dump占用了多大的内存,其中涉及的类有多少,对象有多少,类加载器,如果有没有回收的对象,会有一个连接,可以直接参看(图中的Unreachable Objects Histogram)。 
    比如该例子中显示了Heap dump占用了1.5GB的内存,24.7K个类,48.8m个对象,864个类加载器。 
    然后还会有各种分类信息

histogram视图(重要)

histogram视图主要是查看某个类的实例个数,比如我们在检查内存泄漏时候,要判断是否频繁创建了对象,就可以来看对象的个数来看。也可以通过排序看出占用内存大的对象:

Dominator Tree(重要)

此视图中列出了每个对象(Object Instance)与其引用关系的树状结构,同时包含了占用内存的大小和百分比。通过Dominator Tree视图可以很容易的找出占用内存最多的几个对象(根据Retained Heap或Percentage排序)Histogram视图和Dominator Tree视图的角度不同,前者是基于类的角度,后者是基于对象实例的角度,并且可以更方便的看出其引用关系。

 

这里我们发现有个类占用了大量的内存,并且根据线程id去系统中查询日志定位到了具体的方法,发现两者不谋而合,最后找到了问题,原因是:这个方法一次返回了几十万的数据赋值给list,并且没有缓存,当多个线程请求时就造成了oom,那到底是不是这个问题造成的呢?我们来复现一下。

 

3问题复现:

既然找到了问题那我们就要复现一下这个问题发生的情景,把代码部署到测试环境,数据确保返回的量级和生产的一样,这里我用jmeter来反复调用

我们用jmap -heap xxx(进程id)调用前堆内存使用情况: 

图一:堆初始状态
图二:堆内存用完

 

 

图三:查看系统gc情况
图四:查看系统占用内存实例

 

 

 

图一是初始状态,图二是当我们用jmeter不断调接口后堆的使用情况,由于测试环境堆大小只有1G,所以Eden区和老年代很快就已经满了。图三当使用 jstat -gcutil 查看gc情况发现频繁的youngGC并没有使eden区的使用率下降,而是不断地晋升到老年代,最后fullGC同样没能将老年代的中的对象gc掉。再用jmap -histo 命令查看内存占用排名又发现了我们一开始的那个类。那么问题就确定了。可以着手解决了。

 

 

4解决问题:

问题已经确定了 ,是方法一次加载了几十万的数据造成的,由于数据量大,接口返回的速度较慢,用户可能等待过程中重复点击前端的按钮,这样一次发起了多个请求造成的。我们的解决方法就是前端做一下防止重复提交的限制,点击完未返回数据按钮不可点,后端可以用redis做一下缓存,考虑分批次加载数据;另外这个方法加载的数据需要一定的逻辑处理后的结果才是最终需要用到的,于是我们把这个方法的结果入库,将业务逻辑处理提前完成,避免实时处理。

最后附上出现内存溢出异常时常用的解决办法(转自Java面试那些事):

  1. 检查代码中是否有死循环或递归调用。

  2. 检查是否有循环重复产生新对象实体。

  3. 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询可能就会引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。

  4. 检查List、Map等集合对象是否使用完未清除的问题,List、Map等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

  5. 检查对大文件的读取是否采用nio的方式。

5总结:

排查内存需要了解一些jvm的知识,这样你才能知道问题的来龙去脉,可以有正确的思路去解决它;另外的我们项目用的是ParallelGC,如果你的项目代码已经没有优化的地步了,可以考虑一下jdk1.8以后更流行的G1垃圾回收器。

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值