首页 > 代码库 > Java垃圾回收机制以及内存泄露

Java垃圾回收机制以及内存泄露

1、Java的内存泄露介绍

首先明确一下内存泄露的概念:内存泄露是指程序运行过程动态分配了内存,但是在程序结束的时候这块内存没有被释放,从而导致这块内存不可用,这就是内存

泄露,重启计算机可以解决这个问题,但是有可能再次发生内存泄露,内存泄露与硬件没有关系,它是软件设计的缺陷所导致的。

Java发生内存泄露的原因很明确,就是长声明周期对象持有短声明周期对象的引用就很可能发生内存泄露。尽管短生命周期对象已经不再需要,但是因为长生命

周期对象在持有它的引用而导致它不能被GC回收,这就是Java内存泄露发生的场景。

java内存泄露场景举例:

当我们不断的向集合类内添加元素,而没有相应的删除机制,导致内存一直被占用,这样也不一定就会造成内存泄露,当该集合类只是一个局部变

量的时候,当方法执行完毕退出的时候,自然会被GC所回收,但是怕的是该集合类是一个全局的属性(比如类中的静态变量),那么会导致该集合类占用的内存只

增不减,这样就导致了内存的泄露。所以我们在使用全局性的集合类的时候要注意提供合适的删除策略或者定期清理策略。

内存泄露可以分为4类:

(1)常发性内存泄露:发生内存泄露的代码会多次被执行到,每次执行的时候就会有一块内存泄露

(2)偶发性内存泄露:发生内存泄露的代码只在某些特定环境下才会发生的,常发性与偶发性是相对的,对于特定的环境下,偶发性也就是常发性的。所以,测

试方法和测试环境对检测内存泄露有是很重要的。

(3)一次性内存泄露:发生内存泄露的代码只会被执行一次,也就内存中总有那么一块内存是不可用的。

(4)隐式内存泄露    :程序执行过程中在不断的分配内存,直到程序结束的时候才会释放所有的内存,严格的说,这里并没有发生内存泄露,因为程序最终释放

了所有申请的内存。但是对于一个服务器程序来说,往往会连续执行很多天甚至好几个月的,这样迟早会发生内存溢出的情况。所以,我们称这类内存泄露为隐式

内存泄露。

2、Java内存溢出的问题及解决办法

JVM管理的内存大致分为三种不同类型的内存区域:

Generation space(永久保存区域)、Heap space(堆区域)、JavaStacks(Java栈)。其中永久保存区域主要存放Class(类)和Meta的信息,Class第一次被Load

的时候被放入PermGenspace区域,Class需要存储的内容主要包括方法和静态属性。堆区域用来存放Class的实例(即对象),对象需要存储的内容主要是非静态

属性。每次用new创建一个对象实例后,对象实例存储在堆区域中,这部分空间也被jvm的垃圾回收机制管理。而Java栈跟大多数编程语言包括汇编语言的栈功能相

似,主要基本类型变量以及方法的输入输出参数。Java程序的每个线程中都有一个独立的堆栈。容易发生内存溢出问题的内存空间包括:

Permanent Generation space和Heap space。

第一种OutOfMemoryError PermGenspace

发生这种问题的原意是程序中使用了大量的jarclass,使java虚拟机装载类的空间不够,与PermanentGeneration space有关。解决这类问题有以下两种办法:

(1) 增加java虚拟机中的XX:PermSizeXX:MaxPermSize参数的大小,其中XX:PermSize是初始永久保存区域大小,XX:MaxPermSize是最大永久保存区域大

小。如针对tomcat6.0,在catalina.shcatalina.bat文件中一系列环境变量名说明结束处(大约在70行左右)增加一行:JAVA_OPTS=" -XX:PermSize=64M-

XX:MaxPermSize=128m"如果是windows服务器还可以在系统环境变量中设置。感觉用tomcat发布sprint+struts+hibernate架构的程序时很容易发生这种内存溢出

错误。使用上述方法,我成功解决了部ssh项目的tomcat服务器经常宕机的问题。

(2) 清理应用程序中web-inf/lib下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,可以将共同的jar移到tomcat共同的lib下,减少类的重复加

载。这种方法是网上部分人推荐的,我没试过,但感觉减少不了太大的空间,最靠谱的还是第一种方法。

第二种OutOfMemoryError  Javaheap space

发生这种问题的原因是java虚拟机创建的对象太多,在进行垃圾回收之间,虚拟机分配的到堆内存空间已经用满了,与Heapspace有关。解决这类问题有两种思

路:

(1)检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法。

我以前写一个使用K-Means文本聚类算法对几万条文本记录(每条记录的特征向量大约10来个)进行文本聚类时,由于程序细节上有问题,就导致了Javaheap

 space的内存溢出问题,后来通过修改程序得到了解决

(2)增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:setJAVA_OPTS= -Xms256m -Xmx1024m

3、Java的GC机制

一个优秀的程序员必须了解GC的原理、如何有变化GC的性能、如何与GC进行有限的交互,因为一些程序对性能要求较高,例如嵌入式系统、实时系统等,只有

全面提升内存的管理效率,才能有效提升程序的性能。

GC的基本原理:

Java的内存管理实际上就是对象的管理,包括对象的分配与释放

对于程序员来说,分配对象使用new关键字,释放对象就是将对象的所有引用赋值为null,让程序不能访问到这个对象,我们称之为‘不可达’状态,GC负责回收所

有不可达状态的对象的内存空间。

对于GC来说,当程序员创建了对象,GC就开始监控这个对象的大小、地址、使用情况。通常GC采用有向图的方式来管理堆(heap)中所有对象。通过这种方式

来确定哪些对象是可达的,哪些对象是不可达的,当GC确定了一些对象是不可达状态的时候,GC就有责任将这些对象回收。但是,为了保证GC能够在不同平台实

现的问题,Java规范对GC的很多行为都没有进行严格的规定。例如,对于采用什么类型的回收算法、什么时候进行回收等重要问题都没有明确的规定。因此,不

同的JVM的实现者往往有不同的实现算法。

这也给Java程序员的开发带来行多不确定性。本文研究了几个与GC工作相关的问题,努力减少这种不确定性给Java程序带来的负面影响。 

增量式GC( Incremental GC )

GC在JVM中通常是由一个或一组进程来实现的,它本身也和用户程序一样占用heap空间,运行时也占用CPU.当GC进程运行时,应用程序停止运行。

因此,当GC运行时间较长时,用户能够感到 Java程序的停顿,另外一方面,如果GC运行时间太短,则可能对象回收率太低,这意味着还有很多应该回收的对象没

有被回收,仍然占用大量内存。因此,在设计GC的时候,就必须在停顿时间和回收率之间进行权衡。一个好的GC实现允许用户定义自己所需要的设置,例如有些内

存有限有设备,对内存的使用量非常敏感,希望GC能够准确的回收内存,它并不在意程序速度的放慢。另外一些实时网络游戏,就不能够允许程序有长时间的中

断。增量式GC就是通过一定的回收算法,把一个长时间的中断,划分为很多个小的中断,通过这种方式减少GC对用户程序的影响。虽然,增量式GC在整体性能上

可能不如普通GC的效率高,但是它能够减少程序的最长停顿时间。Sun JDK提供的HotSpot JVM就能支持增量式GC.HotSpot JVM缺省GC方式为不使用增量GC,为

了启动增量GC,我们必须在运行Java程序时增加-Xincgc的参数。

HotSpot JVM增量式GC的实现是采用Train GC算法。它的基本想法就是,将堆中的所有对象按照创建和使用情况进行分组(分层),将使用频繁高和具有相关性

的对象放在一队中,随着程序的运行,不断对组进行调整。当GC运行时,它总是先回收最老的(最近很少访问的)的对象,如果整组都为可回收对象,GC将整组回

收。这样,每次GC运行只回收一定比例的不可达对象,保证程序的顺畅运行。

详解finalize函数

finalize是位于Object类的一个方法,该方法的访问修饰符为protected,由于所有类为Object的子类,因此用户类很容易访问到这个方法。由于,finalize函

数没有自动实现链式调用,我们必须手动的实现,因此finalize函数的最后一个语句通常是super.finalize()

通过这种方式,我们可以实现从下到上实现finalize的调用,即先释放自己的资源,然后再释放父类的资源。根据Java语言规范,JVM保证调用finalize函数之

前,这个对象是不可达的,但是JVM不保证这个函数一定会被调用。另外,规范还保证finalize函数最多运行一次。很多Java初学者会认为这个方法类似与C++中

的析构函数,将很多对象、资源的释放都放在这一函数里面。其实,这不是一种很好的方式。原因有三:

其一,GC为了能够支持finalize函数,要对覆盖这个函数的对象作很多附加的工作。

其二,在finalize运行完成之后,该对象可能变成可达的,GC还要再检查一次该对象是否是可达的。因此,使用 finalize会降低GC的运行性能。

其三,由于GC调用finalize的时间是不确定的,因此通过这种方式释放资源也是不确定的。通常,finalize用于一些不容易控制、并且非常重要资源的释放,例

如一些I/O的操作,数据的连接。这些资源的释放对整个应用程序是非常关键的。在这种情况下,程序员应该以通过程序本身管理(包括释放)这些资源为主,以

finalize函数释放资源方式为辅,形成一种双保险的管理机制,而不应该仅仅依靠finalize来释放资源。


Java垃圾回收机制以及内存泄露