java

位置:IT落伍者 >> java >> 浏览文章

Java内存洩漏分析与解决方案


发布日期:2018年05月01日
 
Java内存洩漏分析与解决方案

Java内存洩漏是每个Java程序员都会遇到的问题程序在本地运行一切正常可是布署到远端就会出现内存无限制的增长最后系统瘫痪那么如何最快最好的检测程序的稳定性防止系统崩盘作者用自已的亲身经历与各位网友分享解决这些问题的办法

作为Internet最流行的编程语言之一Java现正非常流行我们的网络应用程序就主要采用Java语言开发大体上分为客户端服务器和数据库三个层次在进入测试过程中我们发现有一个程序模块系统内存和CPU资源消耗急剧增加持续增长到出现javalangOutOfMemoryError为止经过分析Java内存洩漏是破坏系统的主要因素这里与大家分享我们在开发过程中遇到的Java内存洩漏的检测和处理解决过程

Java是如何管理内存

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

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

在Java中这些无用的对象都由GC负责回收因此程序员不需要考虑这部分的内存洩露虽然我们有几个函数可以访问GC例如运行GC的函数Systemgc()但是根据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

Vector v = new Vector();for (int i = ; i < ; i++){Object o = new Object();vadd(o);o = null;}//此时所有的Object对象都没有被释放因为变量v引用这些对象

实际上这些对象已经是无用的但还被引用GC就无能为力了(事实上GC认为它还有用)这一点是导致内存洩漏最重要的原因再引用另一个例子来说明Java的内存洩漏假设有一个日志类Logger其提供一个静态的log(String msg)任何其它类都可以调用LoggerLog(message)来将message的内容记录到系统的日志文件中

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

几种典型的内存洩漏

我们知道了在Java中确实会存在内存洩漏那么就让我们看一看几种典型的洩漏并找出他们发生的原因和解决方法

全局集合

在大型应用程序中存在各种各样的全局数据仓库是很普遍的比如一个JNDItree或者一个session table在这些情况下必须注意管理储存库的大小必须有某种机制从储存库中移除不再需要的数据

通常有很多不同的解决形式其中最常用的是一种周期运行的清除作业这个作业会验证仓库中的数据然后清除一切不需要的数据

另一种管理储存库的方法是使用反向链接(referrer)计数然后集合负责统计集合中每个入口的反向链接的数目这要求反向链接告诉集合何时会退出入口当反向链接数目为零时该元素就可以从集合中移除了

缓存

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

常用的解决途径是使用javalangrefSoftReference类坚持将对象放入缓存这个方法可以保证当虚拟机用完内存或者需要更多堆的时候可以释放这些对象的引用

类装载器

Java类装载器的使用为内存洩漏提供了许多可乘之机一般来说类装载器都具有复杂结构因为类装载器不仅仅是只与常规对象引用有关同时也和对象内部的引用有关比如数据变量方法和各种类这意味着只要存在对数据变量方法各种类和对象的类装载器那么类装载器将驻留在JVM中既然类装载器可以同很多的类关联同时也可以和静态数据变量关联那么相当多的内存就可能发生洩漏

如何检测和处理内存洩漏

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

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

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

检测内存洩漏的存在

这里我们将简单介绍我们在使用Optimizeit检查的过程通常在知道发生内存洩漏之后第一步是要弄清楚洩漏了什么数据和哪个类的对象引起了洩漏

一般说来一个正常的系统在其运行稳定后其内存的占用量是基本稳定的不应该是无限制的增长的同样对任何一个类的对象的使用个数也有一个相对稳定的上限不应该是持续增长的根据这样的基本假设我们持续地观察系统运行时使用的内存的大小和各实例的个数如果内存的大小持续地增长则说明系统存在内存洩漏如果特定类的实例对象个数随时间而增长(就是所谓的增长率则说明这个类的实例可能存在洩漏情况

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

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

处理内存洩漏的方法

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

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

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

接下来看看有哪些其他的类与洩漏的类的对象相关联前面已经谈到Java中的内存洩漏就是无用的对象保持简单地说就是因为编码的错误导致了一条本来不应该存在的引用链的存在(从而导致了被引用的对象无法释放)因此内存洩漏分析的任务就是找出这条多余的引用链并找到其形成的原因查看对象分配到哪里是很有用的同时只知道它们如何与其他对象相关联(即哪些对象引用了它们)是不够的关于它们在何处创建的信息也很有用

最后进一步研究单个对象看看它们是如何互相关联的借助于Profiler工具应用程序中的代码可以在分配时进行动态添加以创建堆栈跟蹤也有可以对系统中所有对象分配进行动态的堆栈跟蹤这些堆栈跟蹤可以在工具中进行累积和分析对每个被洩漏的实例对象必然存在一条从某个牵引对象出发到达该对象的引用链处于堆栈空间的牵引对象在被从栈中弹出后就失去其牵引的能力变为非牵引对象因此在长时间的运行后被洩露的对象基本上都是被作为类的静态变量的牵引对象牵引

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

上一篇:Java语言编程必备十大技能

下一篇:Weiss的java数据结构与问题解决