怎样解决Java内存泄漏

  Java内存泄漏是每一个Java程序员都会遇到的问题,程序在本地运行一切正常,但是布署到远端就会出现内存无限制的增加,最后系统瘫痪,那么如何最快最好的检测程序的稳定性,防止系统崩盘,做者用自已的亲身经历与各位分享解决这些问题的办法.
 
做为Internet最流行的编程语言之一,Java现正很是流行.咱们的网络应用程序就主要采用Java语言开发,大致上分为客户端、服务器和数据库三 个层次.在进入测试过程当中,咱们发现有一个程序模块系统内存和CPU资源消耗急剧增长,持续增加到出现java.lang.OutOfMemoryError为止.通过分析Java内存泄漏是破坏系统的主要因素.这里与你们分享咱们在开发过程当中遇到的Java内存泄漏的检测和处理解决过程.

一. Java是如何管理内存java

为了判断Java中是否有内存泄露,咱们首先必须了解Java是如何管理内存的.Java的内存管理就是对象的分配和释放问题.在Java中,内存 的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不须要经过调用函数来释放内存,但它只能回收无用而且再也不被其它对象引用的那些对象所占用的空间.程序员

Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍后发现没有被引用的孤立对象就做为垃圾回收.GC为了可以正确释放对 象,必须监控每个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都须要进行监控.监视对象状态是为了更加准确地、及时地释放对象,而释放 对象的根本原则就是该对象再也不被引用.算法

在Java中,这些无用的对象都由GC负责回收,所以程序员不须要考虑这部分的内存泄露.虽然,咱们有几个函数能够访问GC,例如运行GC的函数 System.gc(),可是根据Java语言规范定义,该函数不保证JVM的垃圾收集器必定会执行.由于不一样的JVM实现者可能使用不一样的算法管理 GC.一般GC的线程的优先级别较低.JVM调用GC的策略也有不少种,有的是内存使用到达必定程度时,GC才开始工做,也有定时执行的,有的是平缓执行 GC,有的是中断式执行GC.但一般来讲,咱们不须要关心这些.数据库

二. 什么是Java中的内存泄露编程

致使内存泄漏主要的缘由是,先前申请了内存空间而忘记了释放.若是程序中存在对无用对象的引用,那么这些对象就会驻留内存,消耗内存,由于没法让垃 圾回收器GC验证这些对象是否再也不须要.若是存在对象的引用,这个对象就被定义为"有效的活动",同时不会被释放.要肯定对象所占内存将被回收,咱们就要 务必确认该对象再也不会被使用.典型的作法就是把对象数据成员设为null或者从集合中移除该对象.但当局部变量不须要时,不需明显的设为null,由于一 个方法执行完毕时,这些引用会自动被清理.缓存

在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特色,首先,这些对象是有被引用的,即在有向树形图中,存在树枝通路能够与其 相连;其次,这些对象是无用的,即程序之后不会再使用这些对象.若是对象知足这两个条件,这些对象就能够断定为Java中的内存泄漏,这些对象不会被GC 所回收,然而它却占用内存.服务器

这里引用一个常看到的例子,在下面的代码中,循环申请Object对象,并将所申请的对象放入一个Vector中,若是仅仅释放对象自己,但由于 Vector仍然引用该对象,因此这个对象对GC来讲是不可回收的.所以,若是对象加入到Vector后,还必须从Vector中删除,最简单的方法就是 将Vector对象设置为null.实际上这些对象已是无用的,但还被引用,GC就无能为力了(事实上GC认为它还有用),这一点是致使内存泄漏最重要 的缘由. 再引用另外一个例子来讲明Java的内存泄漏.假设有一个日志类Logger,其提供一个静态的log(String msg),任何其它类均可以调用Logger.Log(message)来将message的内容记录到系统的日志文件中.网络

Logger类有一个类型为HashMap的 静态变量temp,每次在执行log(message)的时候,都首先将message的值写入temp中(以当前线程+当前时间为键),在退出以前再从 temp中将以当前线程和当前时间为键的条目删除.注意,这里当前时间是不断变化的,因此log在退出以前执行删除条目的操做并不能删除执行之初写入的条 目.这样,任何一个做为参数传给log的字符串最终因为被Logger的静态变量temp引用,而没法获得回收,这种对象保持就是咱们所说的Java内存 泄漏. 总的来讲,内存管理中的内存泄漏产生的主要缘由:保留下来却永远再也不使用的对象引用.session

三. 几种典型的内存泄漏数据结构

咱们知道了在Java中确实会存在内存泄漏,那么就让咱们看一看几种典型的泄漏,并找出他们发生的缘由和解决方法.

3.1 全局集合

在大型应用程序中存在各类各样的全局数据仓库是很广泛的,好比一个JNDI-tree或者一个session table.在这些状况下,必须注意管理储存库的大小.必须有某种机制从储存库中移除再也不须要的数据.

一般有不少不一样的解决形式,其中最经常使用的是一种周期运行的清除做业.这个做业会验证仓库中的数据而后清除一切不须要的数据.另外一种管理储存库的方法 是使用反向连接(referrer)计数.而后集合负责统计集合中每一个入口的反向连接的数目.这要求反向连接告诉集合什么时候会退出入口.当反向连接数目为零 时,该元素就能够从集合中移除了.

3.2 缓存
缓存一种用来快速查找已经执行过的操做结果的数据结构.所以,若是一个操做执行须要比较多的资源并会屡次被使用,一般作法是把经常使用的输入数据的操做结果进 行缓存,以便在下次调用该操做时使用缓存的数据.缓存一般都是以动态方式实现的,若是缓存设置不正确而大量使用缓存的话则会出现内存溢出的后果,所以须要 将所使用的内存容量与检索数据的速度加以平衡.

经常使用的解决途径是使用java.lang.ref.SoftReference类坚持将对象放入缓存.这个方法能够保证当虚拟机用完内存或者须要更多堆的时候,能够释放这些对象的引用.

3.3 类装载器
Java类装载器的使用为内存泄漏提供了许多可乘之机.通常来讲类装载器都具备复杂结构,由于类装载器不只仅是只与"常规"对象引用有关,同时也和对象内 部的引用有关.好比数据变量,方法和各类类.这意味着只要存在对数据变量,方法,各类类和对象的类装载器,那么类装载器将驻留在JVM中.既然类装载器可 以同不少的类关联,同时也能够和静态数据变量关联,那么至关多的内存就可能发生泄漏.

四. 如何检测和处理内存泄漏

如何查找引发内存泄漏的缘由通常有两个步骤:第一是安排有经验的编程人员对代码进行走查和分析,找出内存泄漏发生的位置;第二是使用专门的内存泄漏测试工具进行测试.

第一个步骤:在代码走查的工做中,能够安排对系统业务和开发语言工具比较熟悉的开发人员对应用的代码进行了交叉走查,尽可能找出代码中存在的数据库链接声明和结果集未关闭、代码冗余等故障代码.

第二个步骤:就是检测Java的内存泄漏.在这里咱们一般使用一些工具来检查Java程序的内存泄漏问题.市场上已有几种专业检查Java内存泄漏 的工具,它们的基本工做原理大同小异,都是经过监测Java程序运行时,全部对象的申请、释放等动做,将内存管理的全部信息进行统计、分析、可视化.开发 人员将根据这些信息判断程序是否有内存泄漏问题.这些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等.

4.1检测内存泄漏的存在
这里咱们将简单介绍咱们在使用Optimizeit检查的过程.一般在知道发生内存泄漏以后,第一步是要弄清楚泄漏了什么数据和哪一个类的对象引发了泄漏.

通常说来,一个正常的系统在其运行稳定后其内存的占用量是基本稳定的,不该该是无限制的增加的.一样,对任何一个类的对象的使用个数也有一个相对稳 定的上限,不该该是持续增加的.根据这样的基本假设,咱们持续地观察系统运行时使用的内存的大小和各实例的个数,若是内存的大小持续地增加,则说明系统存 在内存泄漏,若是特定类的实例对象个数随时间而增加(就是所谓的"增加率"),则说明这个类的实例可能存在泄漏状况.

另外一方面一般发生内存泄漏的第一个迹象是:在应用程序中出现了OutOfMemoryError.在这种状况下,须要使用一些开销较低的工具来监控和查找内存泄漏.虽然OutOfMemoryError也有可能应用程序确实正在使用这么多的内存;对于这种状况则能够增长JVM可用的堆的数量,或者对应用程序进行某种更改,使它使用较少的内存.

可是,在许多状况下,OutOfMemoryError都是内存泄漏的信号.一种查明方法是不间断地监控GC的活动,肯定内存使用量是否随着时间增长.若是确实如此,就可能发生了内存泄漏.

4.2处理内存泄漏的方法
一旦知道确实发生了内存泄漏,就须要更专业的工具来查明为何会发生泄漏.JVM本身是不会告诉您的.这些专业工具从JVM得到内存系统信息的方法基本上 有两种:JVMTI和字节码技术(byte code instrumentation).Java虚拟机工具接口(Java Virtual Machine Tools Interface,JVMTI)及其前身Java虚拟机监视程序接口(Java Virtual Machine Profiling Interface,JVMPI)是外部工具与JVM通讯并从JVM收集信息的标准化接口.字节码技术是指使用探测器处理字节码以得到工具所需的信息的技 术.

Optimizeit是Borland公司的产品,主要用于协助对软件系统进行代码优化和故障诊断,其中的Optimizeit Profiler主要用于内存泄漏的分析.Profiler的堆视图就是用来观察系统运行使用的内存大小和各个类的实例分配的个数的.

首先,Profiler会进行趋势分析,找出是哪一个类的对象在泄漏.系统运行长时间后能够获得四个内存快照.对这四个内存快照进行综合分析,若是每 一次快照的内存使用都比上一次有增加,能够认定系统存在内存泄漏,找出在四个快照中实例个数都保持增加的类,这些类能够初步被认定为存在泄漏.经过数据收 集和初步分析,能够得出初步结论:系统是否存在内存泄漏和哪些对象存在泄漏(被泄漏).

接下来,看看有哪些其余的类与泄漏的类的对象相关联.前面已经谈到Java中的内存泄漏就是无用的对象保持,简单地说就是由于编码的错误致使了一条 原本不该该存在的引用链的存在(从而致使了被引用的对象没法释放),所以内存泄漏分析的任务就是找出这条多余的引用链,并找到其造成的缘由.查看对象分配 到哪里是颇有用的.同时只知道它们如何与其余对象相关联(即哪些对象引用了它们)是不够的,关于它们在何处建立的信息也颇有用.
  转自:雨后池塘(www.YuHou.net)
最后,进一步研究单个对象,看看它们是如何互相关联的.借助于Profiler工具,应用程序中的代码能够在分配时进行动态添加,以建立堆栈跟踪.也有可 以对系统中全部对象分配进行动态的堆栈跟踪.这些堆栈跟踪能够在工具中进行累积和分析.对每一个被泄漏的实例对象,必然存在一条从某个牵引对象出发到达该对 象的引用链.处于堆栈空间的牵引对象在被从栈中弹出后就失去其牵引的能力,变为非牵引对象.所以,在长时间的运行后,被泄露的对象基本上都是被做为类的静 态变量的牵引对象牵引.

总而言之, Java虽然有自动回收管理内存的功能,但内存泄漏也是不容忽视,它每每是破坏系统稳定性的重要因素.