Java的内存泄漏

问题的提出

Java的一个重要优势就是经过垃圾收集器(Garbage Collection,GC)自动管理内存的回收,程序员不须要经过调用函数来释放内存。所以,不少程序员认为Java不存在内存泄漏问题,或者认为即便有内存泄漏也不是程序的责任,而是GC或JVM的问题。其实,这种想法是不正确的,由于Java也存在内存泄露,但它的表现与C++不一样。html

随着愈来愈多的服务器程序采用Java技术,例如JSP,Servlet, EJB等,服务器程序每每长期运行。另外,在不少嵌入式系统中,内存的总量很是有限。内存泄露问题也就变得十分关键,即便每次运行少许泄漏,长期运行以后,系统也是面临崩溃的危险。程序员

Java是如何管理内存

为了判断Java中是否有内存泄露,咱们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,程序员须要经过关键字new为每一个对象申请内存空间 (基本类型除外),全部的对象都在堆 (Heap)中分配空间。另外,对象的释放是由GC决定和执行的。在Java中,内存的分配是由程序完成的,而内存的释放是有GC完成的,这种收支两条线的方法确实简化了程序员的工做。但同时,它也加剧了JVM的工做。这也是Java程序运行速度较慢的缘由之一。由于,GC为了可以正确释放对象,GC必须监控每个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都须要进行监控。算法

监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象再也不被引用。编程

为了更好理解GC的工做原理,咱们能够将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每一个线程对象能够做为一个图的起始顶点,例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。若是某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么咱们认为这个(这些)对象再也不被引用,能够被GC回收。服务器

如下,咱们举一个例子说明如何用有向图表示内存管理。对于程序的每个时刻,咱们都有一个有向图表示JVM的内存分配状况。如下右图,就是左边程序运行到第6行的示意图。网络


Java使用有向图的方式进行内存管理,能够消除引用循环的问题,例若有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是能够回收它们的。这种方式的优势是管理内存的精度很高,可是效率较低。另一种经常使用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度行低(很难处理循环引用的问题),但执行效率很高。

什么是Java中的内存泄露

下面,咱们就能够描述什么是内存泄漏。在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特色,首先,这些对象是可达的,即在有向图中,存在通路能够与其相连;其次,这些对象是无用的,即程序之后不会再使用这些对象。若是对象知足这两个条件,这些对象就能够断定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。函数

在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,而后却不可达,因为C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,所以程序员不须要考虑这部分的内存泄露。工具

经过分析,咱们得知,对于C++,程序员须要本身管理边和顶点,而对于Java程序员只须要管理边就能够了(不须要管理顶点的释放)。经过这种方式,Java提升了编程的效率。性能


所以,经过以上分析,咱们知道在Java中也有内存泄漏,但范围比C++要小一些。由于Java从语言上保证,任何对象都是可达的,全部的不可达对象都由GC管理。ui

对于程序员来讲,GC基本是透明的,不可见的。虽然,咱们只有几个函数能够访问GC,例如运行GC的函数System.gc(),可是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器必定会执行。由于,不一样的JVM实现者可能使用不一样的算法管理GC。一般,GC的线程的优先级别较低。JVM调用GC的策略也有不少种,有的是内存使用到达必定程度时,GC才开始工做,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但一般来讲,咱们不须要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不但愿GC忽然中断应用程序执行而进行垃圾回收,那么咱们须要调整GC的参数,让GC可以经过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。

下面给出了一个简单的内存泄露的例子。在这个例子中,咱们循环申请Object对象,并将所申请的对象放入一个Vector中,若是咱们仅仅释放引用自己,那么Vector仍然引用该对象,因此这个对象对GC来讲是不可回收的。所以,若是对象加入到Vector后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null。

Vector v=new Vector(10);
for (int i=1;i<100; i++)
{
	Object o=new Object();
	v.add(o);
	o=null;	
}
//此时,全部的Object对象都没有被释放,由于变量v引用这些对象。

如何检测内存泄漏

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

下面,咱们将简单介绍Optimizeit的基本功能和工做原理。

Optimizeit Profiler版本4.11支持Application,Applet,Servlet和Romote Application四类应用,而且能够支持大多数类型的JVM,包括SUN JDK系列,IBM的JDK系列,和Jbuilder的JVM等。而且,该软件是由Java编写,所以它支持多种操做系统。Optimizeit系列还包括Thread Debugger和Code Coverage两个工具,分别用于监测运行时的线程状态和代码覆盖面。

当设置好全部的参数了,咱们就能够在OptimizeIt环境下运行被测程序,在程序运行过程当中,Optimizeit能够监视内存的使用曲线(以下图),包括JVM申请的堆(heap)的大小,和实际使用的内存大小。另外,在运行过程当中,咱们能够随时暂停程序的运行,甚至强行调用GC,让GC进行内存回收。经过内存使用曲线,咱们能够总体了解程序使用内存的状况。这种监测对于长期运行的应用程序很是有必要,也很容易发现内存泄露。


在运行过程当中,咱们还能够从不一样视角观查内存的使用状况,Optimizeit提供了四种方式:

  • 堆视角。 这是一个全面的视角,咱们能够了解堆中的全部的对象信息(数量和种类),并进行统计、排序,过滤。了解相关对象的变化状况。
  • 方法视角。经过方法视角,咱们能够得知每一种类的对象,都分配在哪些方法中,以及它们的数量。
  • 对象视角。给定一个对象,经过对象视角,咱们能够显示它的全部出引用和入引用对象,咱们能够了解这个对象的全部引用关系。
  • 引用图。 给定一个根,经过引用图,咱们能够显示从该顶点出发的全部出引用。

在运行过程当中,咱们能够随时观察内存的使用状况,经过这种方式,咱们能够很快找到那些长期不被释放,而且再也不使用的对象。咱们经过检查这些对象的生存周期,确认其是否为内存泄露。在实践当中,寻找内存泄露是一件很是麻烦的事情,它须要程序员对整个程序的代码比较清楚,而且须要丰富的调试经验,可是这个过程对于不少关键的Java程序都是十分重要的。

综上所述,Java也存在内存泄露问题,其缘由主要是一些对象虽然再也不被使用,但它们仍然被引用。为了解决这些问题,咱们能够经过软件工具来检查内存泄露,检查的主要原理就是暴露出全部堆中的对象,让程序员寻找那些无用但仍被引用的对象。

相关文章
相关标签/搜索