JVM记一次堆内存溢出OutOfMemoryError: Java heap space

项目场景:

前段时间现场报了一个内存溢出的OutOfMemoryError: Java heap space的问题,这个问题十分典型,所以记录下来,希望能帮助到看这篇文章的朋友


问题描述:

问题项目: 这个问题出现在省网的BOSS系统, 这是一个BS架构的系统, 前台使用的Spring Rich Client 也叫Spring胖客户端,Spring Richclient 是一个基于 Spring 的企业级富客户端开发框架,使用该框架,可以轻易构建出具有良好可维护性和可扩展性的企业级 Swing 应用。

问题频次: 经过和现场售后人员沟通,发现问题是偶现现象。只有个别操作员反馈有问题而90%的操作员都反馈操作过程中没有异常现象 

问题日志:从服务器上日志发现日志中有明显的报错信息, 日志中显示OutOfMemoryError: Java heap space:

org.springframework.remoting.RemoteAccessException: Could not access HTTP invoker remote service at http://XXX.XX.XX.XXX:8080/boss/invoke;

nested exception is java.lang.OutOfMemoryError: Java heap space


原因分析:

       Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”,从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等

     从日志上分析OutOfMemoryError: Java heap space异常表示堆内存溢出,堆内存溢出最常出现在两种情况

  1. 程序中已经分配的内存由于某种原因不能释放引起的内存泄露导致内存溢出
  2. 占用内存增加但无法申请到足够的内存空间,空间不足导致内存溢出

从现场反馈中得知“90%的操作员都反馈操作过程中没有异常现象” ,怀疑这个问题和操作员的操作有关,某种操作下会引堆内存发生巨大变化。使用 jvisualvm 查看客户端虚拟机信息变化(jvisualvm  是JDK自带的监控成都新可以监控本地的Java虚拟机也可以监控远程服务),观察一段时间后发现堆内存使用出现了明显上升,表明某个操作创建了大量的产品对象占用的对内存的大量空间

 

通过Dump操作发现有很多产品对象,通过这个信息发现当客户端打开产品管理很套餐管理后通过客户端再给客户做受理操作的时候出现堆内存溢出的情况比较多,两个界面分别显示可使用的产品或者套餐信息,当选中某条产品记录或者套餐记录的时候界面也显示其详情。这个时候分析“产品管理”和“套餐产品管理”的程序代码 发现程序中存在设计问题,

开发人员设计的时候一次性查询出了所有产品列表和产品详情返回给客户端,然后界面选择产品后联动显示详情,分析代码和数据历史, 10年前系统只有几百产品,开发人员用这种设计方法提高了界面联动速度,但是现在再次查询数据库发现数据库里的产品信息已经高到5W。一次处理将5W产品的信息和详情返回给客户端并展示在界面上,这个操作导致堆内存被大量占用并已经块到临界点, 一些其他操作处理时无法申请到足够的空间,导致内存溢出报错


解决方案:

本问题遇到的问题属于第二种情况,内存使用量过大,虚拟机扩展内存的时候已经达到最大值,新的操作无法申请到足够的内存导致内存溢出抛出OutOfMemoryError异常, JAVA虚拟机配置中-Xms 表示初始的堆大小,-Xmx表示最大堆大小,当空余空间小于40%的时候JVM就会增加堆内存大小直到-Xmx最大限制,相反当空余空间大于70%的时候JVM会减少堆直到-Xms 的大小,因此当发现堆内存不足抛出 OutOfMemoryError: Java heap space 异常的时候如果服务器内存足够可以增加-Xmx 配置的值来解决问题,小桶不能装下所有的水,换个大桶来装, 这种方法简单粗暴,虽然能解决问题,但我不建议此方法,更好的做法是先分析问题,找到最优的解法,

产品处理的过程中设计的交互有明显问题,开发人员没有考虑到系统数据的增量变化,使用了一次交互全数据查询的方式,显然这个设计方式不合理, 更好的做法是采用2次交互,先查询出产品信息,当选中产品信息后再去查询产品的详情信息,减少对象创建引起不必要的对内存占用


文章总结:

堆内存溢出原因

  1. 程序中已经分配的内存由于某种原因不能释放引起的内存泄露导致内存溢出
  2. 占用内存增加但无法申请到足够的空间存放数据导致内存溢出

堆内存溢出场景

  • 长生命周期的对象持有短生命周期对象的引用,这是内存泄露最常见的场景,也是代码设计中经常出现的问题,本文中的问题场景就属于这一类
  • 当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段,否则对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中删除当前对象,造成内存泄露。

  • 机器的连接数和关闭时间设置,长时间开启非常耗费资源的连接,也会造成内存泄露。

前一篇:时间管理:重要紧急四象限的思考

Guess you like

Origin blog.csdn.net/Beijing_L/article/details/120024003