关于编程内存泄漏

首先,以java为例,说下gc的有向图回收机制html

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。
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 对象没法释放,从而引发内存泄漏。

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 等。java

下面说下python的垃圾回收机制python

python里也同java同样采用了垃圾收集机制,不过不同的是,python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略。程序员

引用计数机制的优势:数据库

一、简单
二、实时性:一旦没有引用,内存就直接释放了。不用像其余机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。        
引用计数机制的缺点: 
一、维护引用计数消耗资源 
二、循环引用 
 
下面说下什么事标记清除和分代收集
一、标记-清除机制

标记-清除机制,顾名思义,首先标记对象(垃圾检测),而后清除垃圾(垃圾回收)。如图1:编程

image

                            图1数组

首先初始全部对象标记为白色,并肯定根节点对象(这些对象是不会被删除),标记它们为黑色(表示对象有效)。将有效对象引用的对象标记为灰色(表示对象可达,服务器

但它们所引用的对象还没检查),检查完灰色对象引用的对象后,将灰色标记为黑色。重复直到不存在灰色节点为止。最后白色结点都是须要清除的对象。网络

二、回收对象的组织模块化

这里所采用的高级机制做为引用计数的辅助机制,用于解决产生的循环引用问题。而循环引用只会出如今“内部存在能够对其余对象引用的对象”,好比:list,class等。

为了要将这些回收对象组织起来,须要创建一个链表。天然,每一个被收集的对象内就须要多提供一些信息,下面代码是回收对象里必然出现的。

一个对象的实际结构如图2:

                  image

                           图2

经过PyGC_Head的指针将每一个回收对象链接起来,造成了一个链表,也就是在1里提到的初始化的全部对象。

三、分代技术

分代技术是一种典型的以空间换时间的技术,这也正是java里的关键技术。这种思想简单点说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集。

这样的思想,能够减小标记-清除机制所带来的额外操做。分代就是将回收对象分红数个代,每一个代就是一个链表(集合),代进行标记-清除的时间与代内对象

存活时间成正比例关系。

从上面代码能够看出python里一共有三代,每一个代的threshold值表示该代最多容纳对象的个数。默认状况下,当0代超过700,或1,2代超过10,垃圾回收机制将触发。

0代触发将清理全部三代,1代触发会清理1,2代,2代触发后只会清理本身。

转载自:http://www.cnblogs.com/mingaixin/archive/2013/01/31/2886680.html

相关文章
相关标签/搜索