充满BUG的世界观——再遇Java内存泄露

发现内存泄露除了仔细看代码的确没有太好的方法。首先看gc log, 确定是内存泄露,而不是内存不够。内存泄露的特点就是以每次Full GC后使用的最低内存为起点,拟合一条线。如果这条线是随时间递增的一条曲线,那么很大程度上代表着内存泄露。

然后使用 jmap -histo [pid] 来查看你的所有对象所占内存的比例。你可能很不幸的发现[B 这个byte数组对象占用了绝大多数。这的确没有更好的方法了。只能一点点的看代码。检查一下有没有写成循环的地方。检查一下有没有申请的内存没有释放。检查一下全局变量或者单例中的map啥的。最后,你大概只能以怀疑一切的态度检查所有的代码。

ok. 下面以八卦的方式讲讲我这次遇到的内存泄露(memory leak).

这几天一直在写一个入库组件。这个组件的目的就是解析传输过来的数据,并写入到数据库中。嗯。听起来很简单。但是解析格式比较复杂,而且还要使用某种特定的计算公式进行去重处理。另外解析的数据需要以上千条记录的形式输入到数据库中。嗯,为什么会这样设计呢?因为历史遗留问题。。。嗯历史遗留问题这几个字非常管用。不管放在什么语境中,反正觉得困难就可以说历史遗留问题。其实是为了小步快跑,慢慢申请时间。上头大概不会有人希望你这模块做大半年还没有做来。希望你这么做的,大概都是你的死敌。

好吧上面就历史背景。在这个背景下。我这边入库组件,写了一个分布式的,多线程解析,多线程批量入库的代码。本来觉得这种设计挺好。但是因为gap lock, 多线程和分布式的问题,批量写总是在不知不觉中造成死锁。为了代码的精简,为了不引入更多的问题,索性将批量写改为了单条写。

好了,这下memory leak问题来了。难道是因为之前都死锁了,所以memory leak 就没有暴露出来? 有可能哦~

先看一下gc log。为啥是memory leak。看一下下图。其中蓝线的是内存使用情况。蓝线的最低点是每次Full GC 带来的内存大量释放。GC的最低点你可以看到是不断增加的。所以很大的可能是内存泄露。

为什么说很大可能是内存泄露呢?不同的程序有不同的内存使用模式。比如说我的程序中有一个大map. 这个map会不断的填充数据。但是数据集是有限的。但是这个map在最终填充完毕前,内存的使用量会不断的增长。如果内存不够了,仍然会有outofmemory 错误,并且那个gc的图和我给的gc图会很像。这不代表这内存泄露,这只能说内存不够用。但是这需要先证明数据集是有限的,并且系统空闲内存可以完全放入这个数据集。否则你只能采取其他方法来防范内存不够用的情况。

好吧。我看到这个图。感觉是内存泄露。为啥?因为我的数据集有限,并且粗略算了一下也不大。即使有缓存计算的策略存在,这些空间仍然不会造成outofMemory的现象。那到底是什么原因?先使用一下jmap -histo [pid]. 看了一下比例。

 num     #instances         #bytes  class name
----------------------------------------------
   1:      21571308     1163654064  [B
   2:       1770275      125384008  [I
   3:       1715985      120562976  [[B
   4:       1715382      120535928  [Ljava.io.InputStream;
   5:       3430930      109789592  [Z
   6:       1715198       68607920  com.mysql.jdbc.PreparedStatement$BatchParams
   7:        621372       44778960  [C
   8:         59015       11608344  [Ljava.lang.Object;
   9:        469551       11269224  java.lang.String
  10:        335730        8057520  org.dom4j.tree.DefaultAttribute
  11:         76733        2455456  org.dom4j.tree.DefaultElement
  12:         49621        2376880  [Ljava.lang.String;
  13:         47685        1525920  java.util.HashMap$Node
  14:         41482        1327424  com.paratera.importdata.CacheKeys
  15:         46753        1122072  java.lang.StringBuilder
  16:         44715        1073160  java.util.ArrayList
  17:         32577        1042464  java.util.concurrent.ConcurrentHashMap$Node
  18:         40990         983760  java.lang.Long
  19:         13911         667728  java.nio.HeapCharBuffer
  20:         13832         663936  java.nio.HeapByteBuffer
  21:         24729         593496  java.lang.StringBuffer
  22:         13476         539040  [Ljava.util.Formatter$Flags;

哦哦哦~排名第一的是byte[] , 我X. 其实根本差不出来是什么原因。除非你的代码里,很多 new byte[]. 否则使用byte[] 基本是你的调用的各种组件里的byte[]. 好吧。现在的怀疑的对象扩展到了自己的全部组件。。

前五名都看不出任何问题。直到第六名。

就是这货。这货byte[] byte[][] , int[] , InputStream[] 排名前几的都有!!!不用说了。一定是这货。这货就是mysql jdbc执行 addBatch() 放入的。然后我细细看了一下代码。发现 executeBatch() 调用才会清理了 addBatch()中的 BatchParam, 如果只是调用了execute()方法,则写入数据库时不会清理BatchParam. 所以还是之前将批量入库转换为单条数据入库导致的。只将executeBatch()改为了execute(), 虽然功能上立马变成了单条数据入库。但实际上却直接引入了内存泄露问题。

当然因为代码封装/函数化的问题, addBatch() 和 executeBatch() 被放入到了不同的函数中。。。。所以再次自己挖坑自己埋。自己掉坑里,一定是自己之前坑挖的不对。最后的解决方法也很简单。将addBatch()去掉就立马好了。

好了。。。。写的多了。。。天晚了。。。洗洗睡去了。。。

哦对了。打条公司信息,有想加入【并行科技】java团队的发简历到[email protected](请注明来源,方便过hr关).  公司只做高大上的超算/高性能计算的 toB  toG 业务,不做 toC业务。待遇不是问题。问题是你到底值不值你要的待遇。。。

时间: 2024-10-13 12:24:33

充满BUG的世界观——再遇Java内存泄露的相关文章

Java内存泄露的理解与解决

Java内存管理机制 在C++语言中,如果需要动态分配一块内存,程序员需要负责这块内存的整个生命周期.从申请分配.到使用.再到最后的释放.这样的过程非常灵活,但是却十分繁琐,程序员很容易由于疏忽而忘记释放内存,从而导致内存的泄露.Java语言对内存管理做了自己的优化,这就是垃圾回收机制.Java的几乎所有内存对象都是在堆内存上分配(基本数据类型除外),然后由GC(garbage collection)负责自动回收不再使用的内存. 上面是Java内存管理机制的基本情况.但是如果仅仅理解到这里,我们

Java 内存泄露(二)

一.Java内存回收机制 不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址.Java中对象是采用new或者反射的方法创建的(还有克隆),这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的.GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请.引用.被引用.赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环

黑马程序员——hashCode方法的作用,java内存泄露

hashCode方法的作用:当有一个对象要存入hash集合的时候,JVM首先会调用hashCode方法获取该对象的哈希值,然后根据哈希值找到相应的存储区域,最后取出该区域的所有元素与该对象进行equals比较,如果相等,不存入该元素,否则,存入.这样不用遍历集合中的所有元素就能的到我们想要的结果,提高了查找的效率.但是如果不覆写hashCode方法的话,相同的对象可能会存储在HashSet集合中,虽然他们equals比较相同,但他们的内存区域不同的话,就不会进行equals比较了.为了让两个相同

Java内存泄露及性能调优

内存泄漏及解决方法 1.系统崩溃前的一些现象:每次垃圾回收的时间越来越长,由之前的10ms延长到50ms左右,FullGC的时间也有之前的0.5s延长到4.5sFullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC年老代的内存越来越大并且每次FullGC后年老代没有内存被释放 之后系统会无法响应新的请求,逐渐到达OutOfMemoryError的临界值. 2.生成堆的dump文件 通过JMX的MBean生成当前的Heap信息,大小为一个3G(整个堆的大小)的hprof文件,如果没

Java 内存泄露的理解与解决过程

本文详细地介绍了Java内存管理的原理,以及内存泄露产生的原因,同时提供了一些列解决Java内存泄露的方案,希望对各位Java开发者有所帮助. Java内存管理机制 在C++ 语言中,如果需要动态分配一块内存,程序员需要负责这块内存的整个生命周期.从申请分配.到使用.再到最后的释放.这样的过程非常灵活,但是却十分繁琐,程序员很容易由于疏忽而忘记释放内存,从而导致内存的泄露. Java 语言对内存管理做了自己的优化,这就是垃圾回收机制. Java 的几乎所有内存对象都是在堆内存上分配(基本数据类型

JAVA 内存泄露详解

一.Java内存回收机制  不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址.Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的.GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请.引用.被引用.赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题.在

动手探究Java内存泄露问题

在本系列教程中,将带大家动手探究Java内存泄露之谜,并教授给读者相关的分析方法.以下是一个案例. 最近有一个服务器,经常运行的时候就出现过载宕机的现象.重启脚本和系统后,该个问题还是会出现.尽管有大量的数据丢失,但因不是关键业务,问题并 不严重.不过还是决定作进一步的调查,来看下问题到底出现在哪.首先注意到的是,服务器通过了所有的单元测试和完整的集成环境的测试.在测试环境下使用测 试数据时运行正常,那么为什么在生产环境中运行会出现问题呢?很容易会想到,也许是因为实际运行时的负载大于测试时的负载

java内存泄露详解

很多人有疑问,java有很好的垃圾回收机制,怎么会有内存泄露?其实是有的,那么何为内存泄露?在Java中所谓内存泄露就是指在程序运行的过程中产生了一些对象,当不需要这些对象时,他们却没有被垃圾回收掉,而且程序运行中很难发现这个对象,它始终占据着内存却没有发挥作用. 我举这样一个例子,在现实开发中我们需要自定义一个先进后出的栈集合,代码如下: 这个代码看起来和运行起来都没问题,但是,这里有个很隐晦的问题,就是在pop()方法里面,我们首先找到集合最后一个元素的下标,然后按照下标从集合中取出,但是这

关于java内存泄露的总结--引用的类型:强引用,弱引用,软引用

今天面试了一家公司的java开发方面的实习生,被问到一个问题:如何处理java中的内存泄露问题,保证java的虚拟机内存不会被爆掉,当时其实觉得面试官的问题有点泛,所以也没有很好领会他的意思,答案也不是很准确,后来回去查了下资料,大概明白面试官要问的东西是什么(尴尬,才反应过来),然后,也特地简单总结下java内存溢出的相关内容,以备之后复习. 一.什么情况下会java内存泄露? java不是有GC吗?为毛还会内存泄露?之前我也一直以为,java有垃圾回收器在,估计内存泄露的情况一般不会发生吧?