Java的一个重要优势就是经过垃圾收集器(Garbage Collection,GC)自动管理内存的回收,程序员不须要经过调用函数来释放内存。所以,不少程序员认为Java不存在内存泄漏问题,或者认为即便有内存泄漏也不是程序的责任,而是GC或JVM的问题。其实,这种想法是不正确的,由于Java也存在内存泄露,但它的表现与C++不一样。html
随着愈来愈多的服务器程序采用Java技术,例如JSP,Servlet, EJB等,服务器程序每每长期运行。另外,在不少嵌入式系统中,内存的总量很是有限。内存泄露问题也就变得十分关键,即便每次运行少许泄漏,长期运行以后,系统也是面临崩溃的危险。程序员
为了判断Java中是否有内存泄露,咱们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,程序员须要经过关键字new为每一个对象申请内存空间 (基本类型除外),全部的对象都在堆 (Heap)中分配空间。另外,对象的释放是由GC决定和执行的。在Java中,内存的分配是由程序完成的,而内存的释放是有GC完成的,这种收支两条线的方法确实简化了程序员的工做。但同时,它也加剧了JVM的工做。这也是Java程序运行速度较慢的缘由之一。由于,GC为了可以正确释放对象,GC必须监控每个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都须要进行监控。算法
监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象再也不被引用。编程
为了更好理解GC的工做原理,咱们能够将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每一个线程对象能够做为一个图的起始顶点,例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。若是某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么咱们认为这个(这些)对象再也不被引用,能够被GC回收。服务器
如下,咱们举一个例子说明如何用有向图表示内存管理。对于程序的每个时刻,咱们都有一个有向图表示JVM的内存分配状况。如下右图,就是左边程序运行到第6行的示意图。网络
下面,咱们就能够描述什么是内存泄漏。在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也存在内存泄露问题,其缘由主要是一些对象虽然再也不被使用,但它们仍然被引用。为了解决这些问题,咱们能够经过软件工具来检查内存泄露,检查的主要原理就是暴露出全部堆中的对象,让程序员寻找那些无用但仍被引用的对象。