java内存泄露

1 引言 
     Java的一个重要优势就是经过垃圾收集器GC (Garbage Collection)自动管理内存的回收,程序员不须要经过调用函数来释放内存。所以,不少程序员认为Java 不存在内存泄漏问题,或者认为即便有内存泄漏也不是程序的责任,而是GC 或JVM的问题。其实,这种想法是不正确的,由于Java 也存在内存泄漏,但它的表现与C++不一样。若是正在开发的Java 代码要全天24 小时在服务器上运行,则内存漏洞在此处的影响就比在配置实用程序中的影响要大得多,即便最小的漏洞也会致使JVM耗尽所有可用内存。另外,在不少嵌入式系统中,内存的总量很是有限。在相反的状况下,即使程序的生存期较短,若是存在分配大量临时对象(或者若干吞噬大量内存的对象)的任何Java 代码,并且当再也不须要这些对象时也没有取消对它们的引用,则仍然可能达到内存极限。   
2 Java 内存回收机制 
     Java 的内存管理就是对象的分配和释放问题。分配内存的方式多种多样,取决于该种语言的语法结构。但不管是哪种语言的内存分配方式,最后都要返回所分配的内存块的起始地址,即返回一个指针到内存块的首地址。在Java 中全部对象都是在堆(Heap)中分配的,对象的建立一般都是采用new或者是反射的方式,但对象释放却有直接的手段,因此对象的回收都是由Java虚拟机经过垃圾收集器去完成的。这种收支两条线的方法确实简化了程序员的工做,但同时也加剧了JVM的工做,这也是Java 程序运行速度较慢的缘由之一。由于,GC 为了可以正确释放对象,GC 必须监控每个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC 都须要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象再也不 
被引用。Java 使用有向图的方式进行内存管理,能够消除引用循环的问题,例若有三个对象,相互引用,只要它们和根进程不可达,那么GC 也是能够回收它们的。在Java 语言中,判断一块内存空间是否符合垃圾收集器收集标准的标准只有两个:一个是给对象赋予了空值null,如下再没有调用过,另外一个是给对象赋予了新值,即从新分配了内存空间。  
3 Java 中的内存泄漏  
3.1 Java 中内存泄漏与C++的区别 
    在Java 中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特色,首先,这些对象是可达的,即在有向图中,存在通路能够与其相连;其次,这些对象是无用的,即程序之后不会再使用这些对象。若是对象知足这两个条件,这些对象就能够断定为Java 中的内存泄漏,这些对象不会被GC 所回收,然而它却占用内存。在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,而后却不可达,因为C++中没有GC,这些内存将永远收 
不回来。在Java 中,这些不可达的对象都由GC 负责回收,所以程序员不须要考虑这部分的内存泄漏。经过分析,能够得知,对于C++,程序员须要本身管理边和顶点,而对于Java 程序员只须要管理边就能够了(不须要管理顶点 的释放)。经过这种方式,Java 提升了编程的效率。  
3.2 内存泄漏示例 3.2.1 示例1 
   在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,若是仅仅释放引用自己,那么Vector 仍然引用该对象,因此这个对象对GC 来讲是不可回收的。所以,若是对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。 java

Vector v = new Vector(10); 程序员

for (int i = 1; i<100; i++) 数据库

{Object o = new Object(); v.add(o); o = null; }//  编程

此时,全部的Object 对象都没有被释放,由于变量v 引用这些对象。实际上无用,而还被引用的对象,GC 就无能为力了(事实上GC 认为它还有用),这一点是致使内存泄漏最重要的缘由。  
(1)若是要释放对象,就必须使其的引用记数为0,只有那些再也不被引用的对象才能被释放,这个原理很简单,可是很重要,是致使内存泄漏的基本缘由,也是解决内存泄漏方法的宗旨; 数组

(2)程序员无须管理对象空间具体的分配和释放过程,但必需要关注被释放对象的引用记数是否为0; 服务器

(3)一个对象可能被其余对象引用的过程的几种: 网络

a.直接赋值,如上例中的A.a = E; 
b.经过参数传递,例如public void addObject(Object E); c.其它一些状况如系统调用等。   
3.3 容易引发内存泄漏的几大缘由 模块化

3.3.1 静态集合类 
像HashMap、Vector 等静态集合类的使用最容易引发内存泄漏,由于这些静态变量的生命周期与应用程序一致,如示例1,若是该Vector 是静态的,那么它将一直存在,而其中全部的Object对象也不能被释放,由于它们也将一直被该Vector 引用着。 函数

3.3.2 监听器 
在java 编程中,咱们都须要和监听器打交道,一般一个应用当中会用到不少监听器,咱们会调用一个控件的诸如addXXXListener()等方法来增长监听器,但每每在释放对象的时候却没有记住去删除这些监听器,从而增长了内存泄漏的机会。 工具

3.3.3 物理链接 
一些物理链接,好比数据库链接和网络链接,除非其显式的关闭了链接,不然是不会自动被GC 回收的。Java 数据库链接通常用DataSource.getConnection()来建立,当再也不使用时必须用Close()方法来释放,由于这些链接是独立于JVM的。对于Resultset 和Statement 对象能够不进行显式回收,但Connection 必定要显式回收,由于Connection 在任什么时候候都没法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会当即为NULL。可是若是使用链接池,状况就不同了,除了要显式地关闭链接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另一个也会关闭),不然就会形成大量的Statement 对象没法释放,从而引发内存泄漏。   

 (window.cproArray = window.cproArray || []).push({ id: "u2280119" });

 3.3.4 内部类和外部模块等的引用 

内部类的引用是比较容易遗忘的一种,并且一旦没释放可能致使一系列的后继类对象没有释放。对于程序员而言,本身的程序很清楚,若是发现内存泄漏,本身对这些对象的引用能够很快定位并解决,可是如今的应用软件 并不是一我的实现,模块化的思想在现代软件中很是明显,因此程序员要当心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如: public void registerMsg(Object b); 
这种调用就要很是当心了,传入了一个对象,极可能模块B就保持了对该对象的引用,这时候就须要注意模块B 是否提供相应的操做去除引用。   
4 预防和检测内存漏洞 
    在了解了引发内存泄漏的一些缘由后,应该尽量地避免和发现内存泄漏。 
(1)好的编码习惯。最基本的建议就是尽早释放无用对象的引用,大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域后,自动设置为null。在使用这种方式时候,必须特别注意一些复杂的对象图,例如数组、列、树、图等,这些对象之间有相互引用关系较为复杂。对于这类对象,GC 回收它们通常效率较低。若是程序容许,尽早将不用的引用对象赋为null。另外建议几点: 
在确认一个对象无用后,将其全部引用显式的置为null; 
当类从Jpanel 或Jdialog 或其它容器类继承的时候,删除该对象以前不妨调用它的removeall()方法;在设一个引用变量为null 值以前,应注意该引用变量指向的对象是否被监听,如有,要首先除去监听器,而后才能够赋空值;当对象是一个Thread 的时候,删除该对象以前不妨调用它的interrupt()方法;内存检测过程当中不只要关注本身编写的类对象,同时也要关注一些基本类型的对象,例如:int[]、String、char[]等等;若是有数据库链接,使用try...finally 结构,在finally 中关闭Statement 对象和链接。 
(2)好的测试工具。在开发中不能彻底避免内存泄漏,关键要在发现有内存泄漏的时候能用好的测试工具迅速定位问题的所在。市场上已有几种专业检查Java 内存泄漏的工具,它们的基本工做原理大同小异,都是经过监测Java 程序运行时,全部对象的申请、释放等动做,将内存管理的全部信息进行统计、分析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括Optimizeit Profiler、JProbe Profiler、JinSight、Rational 公司的Purify 等。   
记:     映像(Reflector)是一个程序分析本身的能力。java.lang.reflect包提供了获取关于字段、构造函数、方法和类的修改器的信息的能力。利用这些信息能够创建和Java Beans组件打交道的工具。能够动态建立组件的特征。 
    堆(heap) :栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不一样,Java自动管理栈和堆,程序员不能直接地设置栈或堆。栈的优点是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是肯定的,缺少灵活性。另外,栈数据能够共享,堆的优点是能够动态地分配内存大小,生存期也没必要事先告诉编译器,Java的垃圾收集器会自动收走这些再也不使用的数据。但缺点是,因为要在运行时动态分配内存,存取速度较慢。 
    链接池:在实际应用开发中,特别是在WEB应用系统中,若是JSP、Servlet或EJB使用JDBC直接访问数据库中的数据,每一次数据访问请求都必须经历创建数据库链接、打开数据库、存取数据和关闭数据库链接等步骤,而链接并打开数据库是一件既消耗资源又费时的工做,若是频繁发生这种数据库操做,系统的性能必然会急剧降低,甚至会致使系统崩溃。数据库链接池技术是解决这个问题最经常使用的方法,在许多应用程序服务器(例如:Weblogic,WebSphere,JBoss)中,基本都提供了这项技术,无需本身编程,可是,深刻了解这项技术是很是必要的。 

数据库链接池技术的思想很是简单,将数据库链接做为对象存储在一个Vector对象中,一旦数据库链接创建后,不一样的数据库访问请求就能够共享这些链接,这样,经过复用这些已经创建的数据库链接,能够克服上述缺点,极大地节省系统资源和时间。   数据库链接池的主要操做以下:   (1)创建数据库链接池对象(服务器启动)。   (2)按照事先指定的参数建立初始数量的数据库链接(即:空闲链接数)。   (3)对于一个数据库访问请求,直接从链接池中获得一个链接。若是数据库链接池对象中没有空闲的链接,且链接数没有达到最大(即:最大活跃链接数),建立一个新的数据库链接。   (4)存取数据库。   (5)关闭数据库,释放全部数据库链接(此时的关闭数据库链接,并不是真正关闭,而是将其放入空闲队列中。如实际空闲链接数大于初始空闲链接数则释放链接)。   (6)释放数据库链接池对象(服务器中止、维护期间,释放数据库链接池对象,并释放全部链接)。