探究java垃圾回收机制

java垃圾回收机制使得java程序员不用手动去释放通过new关键字在heap上申请的空间 。但是,任何事情都是有利有弊。它也许并不像我们想象中那样强大!让我们来一探究竟。本blog链接:blog.csdn.com/whuslei。  
   阅读本文前,请务必参看《think in java》第四版的第五章"初始化和清理"。其他网上的资料就没必要看了,没有太大价值。    
    有几个问题应该考虑下: 
1、什么是垃圾?它是如何形成的? 
2、java的垃圾收集器是什么?它是什么时候执行的?执行过程是怎样的? 
3、sun为什么不推荐使用finalize()函数来释放一个对象?这个函数究竟做了些什么? 
4、java中是否也存在内存泄漏?是否和C++中的一样?如果存在,应当如何避免?

   带着问题来思考java内部是怎么做的。 

一、对象在什么情况下变为垃圾? 
   java中称那些不可达的对象为垃圾 。那什么叫不可达?其实就是没办法再引用到该对象了。主要有以下几种情况使对象变为垃圾: 

1、对非线程对象来说,所有的活动线程都不能访问该对象,那么该对象就变为垃圾。 
2、对线程对象来说,满足上面的条件,且线程未启动或已停止

例如: 
(1)改变对象的引用,如置为null或者指向其他对象。 
   Object x=new Object();//object1 
   Object y=new Object();//object2 
   x=y;//object1 变为垃圾 
   x=y=null;//object2 变为垃圾 

(2)超出作用域 
   if(i==0){ 
      Object x=new Object();//object1 
   }//括号结束后object1将无法被引用,变为垃圾 

(3)类嵌套导致未完全释放 
   class A{ 
      A a; 
   } 
   A x= new A();//分配一个空间 
   x.a= new A();//又分配了一个空间 
   x=null;//将会产生两个垃圾 

(4)线程中的垃圾 
   class A implements Runnable{   
     void run(){ 
       //.... 
     } 
   } 
   //main 
   A x=new A();//object1 
   x.start(); 
   x=null;//等线程执行完后object1才被认定为垃圾

   这样看,确实在代码执行过程中会产生很多垃圾,不过不用担心,java可以有效地处理他们。

二、垃圾收集机制 
   首先,垃圾收集机制是由垃圾收集器Garbage Collection(以下简称gc)来实现的。而gc其实就是一个后台守护进程(其实这种说法不是很确切,因为当可用内存不足时,gc会先暂停应用程序 )。它的特别之处在于它是一个低优先级的进程,但是可以根据内存的使用情况动态地调整它的优先级。因此,通常在系统内存低到一定限度时才会自动运行,从而实现对内存的回收。这也就是我们通常说垃圾回收的时间是不确定的原因。 
   为什么要这样设计呢?我们知道,gc也是进程,也要消耗cpu等资源,如果gc执行过于频繁会对java程序的执行产生较大影响(java解释器本来就不快) 。因此JVM的设计者们选择了不定期的gc。 

   在垃圾回收器回收内存前,还需要做一些清理工作。why? 

   其实,gc只能回收通过new关键字申请的内存(在堆上),但是堆上的内存却不完全是通过new申请分配的 !java 中通过JNI可以访问本地方法,一般是调用C,而c是通过malloc和free来进行申请内存的。这部分"特殊"内存如果不手动释放,会引起内存泄 漏!gc是无法回收这部分内存的!举例:System.gc()可以显示调用垃圾收集器。从java api中对这个函数的解释可以看出,这个方法只是一种尽力而为 的回收,并不能保证将所有的垃圾都回收,为什么不能保证回收全部垃圾?原因如上。 
   所以,java允许在类中定义一个名为finalize的方法(Object类中的一个方法)。它可以用来做一些清理工作。它的工作原理"假定"是这样的:
一旦JVM的垃圾回收器准备好释放对象所占用的内存空间,将首先调用其finalize方法(每个类都有,他们都继承自Object类),并且在下一次垃圾回收动作时才会真正回收对象所占用的内存空间。 

   对上面的原理,很容易产生疑惑,如下: 
   
1、gc是负责回收内存的,那finalize也是用来回收内存的,他们一样吗? 
   答:通过创建对象产生的内存,gc可以回收(至于如何回收,后面会讲到)。但是有些特殊内存gc不能回收,因此可以在finalize中用本地方法(native method)如free操作等! 

   
2、一个对象的finalize方法有什么要注意的? 
   答:一个对象的finalize方法只能被调用一次!它并没有减少垃圾回收器的工作量!执行finalize后,gc依然需要判断这块内存空间是否可以回收! 

   
3、为什么sun并不推荐用finalize方法来完成清理工作? 
   答:finalize方法只在垃圾回收之前才被调用。但是我们知道,gc本来就是不确定的。因此,当一个对象变为垃圾后,我们并不知道gc何时运行,也就不知道finalize何时被调用了。有可能它永远都不被调用!( 程序退出时,未回收的内存将直接交还给操作系统)。由此可知,依靠finalize方法来完成清理工作是不靠谱的。
正确的方法是,在放弃这个对象前调用相应的方法来进行内存的释放!(显式地调用)。 

    4、有人可能会问,显式调用System.gc()强制运行gc不也可以调用到finalize完成清理吗?  
   答:我认为,不可取。因为每次想执行清理都需要调用gc()来强制启动垃圾处理器,这样垃圾回收率低,反而很影响程序效率! 为何不将清理工作放到某个方法中,准备放弃该对象的内存空间之前就调用该方法!

    重点来了,如何回收内存? 
    方法一:引用计数法 简单但速度很慢。缺陷是:不能处理循环引用的情况。 用法如下:每个对象都含有一个引用计数器,当被连接到对象时计数器加1,当离开作用域或者被置为null时,引用计数器减1。垃圾回收时,释放计数器为0的内存空间。如果出现循环引用,则会出现"对象应该被回收,但计数器不为0"。 
    方法二:停止-复制(stop and copy)。 效率低,需要的空间大。 用法如下:先暂停程序的运行,将所有存活的对象从一个堆复制到另一个堆。没有被复制的都是垃圾,被复制部分将是连续空间没有碎片。 
    方法三:标记-清扫(mark and sweep)。 速度较快,占用空间少,但是释放后空间不来连续。 用法如下:先暂停程序的运行,遍历所有引用,每找到一个存活对象就给它一个标记,此过程中不进行回收。当标记结束后才开始清理。清理后,剩下的部分是不连续的。如果希望等到连续的空间则需要进行复制。

   JAVA虚拟机中是如何做的? 
   java的做法很聪明,我们称之为"自适应"的垃圾回收器,或者是"自适应的、分代的、停止-复制、标记-清扫"式垃圾回收器 。它会根据不同的环境和需要选择不同的处理方式。 

三、java中的内存泄漏 
   从泄漏的内存位置角度可以分为两种:JVM 中  Java Heap 的内存泄漏 ;JVM 内存中  native memory 的内存泄漏  
  
1、java heap的内存泄漏。 
   由于java heap的空间是在jvm过程中动态变化的。如果Java对象越来越多,占据Java Heap的空间也越来越大,JVM会在运行时扩充Java Heap的容量。如果Java Heap容量扩充到上限,并且在GC后仍然没有足够空间分配新的Java对象,便会抛出out of memory异常,导致JVM进程崩溃 。Java Heap 中 out of memory 异常的出现有两种原因——①程序过于庞大,致使过多Java对象的同时存在;②程序编写的错误导致Java Heap内存泄漏。多种原因可能导致Java Heap内存泄漏。JNI编程错误也可能导致Java Heap的内存泄漏。

    2、JVM 中 native memory 的内存泄漏 
   从操作系统角度看,JVM 在运行时和其它进程没有本质区别。在系统级别上,它们具有同样的调度机制,同样的内存分配方式,同样的内存格局 。JVM进程空间中,Java Heap以外的内存空间称为JVM的native memory 。 进程的很多资源都是存储在JVM的native memory中,例如载入的代码映像,线程的堆栈,线程的管理控制块,JVM的静态数据、全局数据等等。也包括JNI程序中native code分配到的资源。在JVM运行中,多数进程资源从native memory中动态分配。当越来越多的资源在 native memory中分配,占据越来越多native memory空间并且达到native memory上限时,JVM会抛出异常,使JVM进程异常退出。而此时Java Heap往往还没有达到上限。多种原因可能导致JVM的native memory内存泄漏。例如JVM在运行中过多的线程被创建,并且在同时运行。JVM 为线程分配的资源就可能耗尽native memory的容量。

   本文版权所有,如有转载,请附上链接:blog.csdn.com/whuslei。

   参考文章如下: 
    http://blog.csdn.net/youhaodeyi/archive/2005/12/16/553891.aspx  
    http://blog.csdn.net/zhaosongpine/archive/2009/04/11/4063756.aspx  
    http://www.ibm.com/developerworks/cn/java/j-lo-jnileak/index.html?ca=drs-

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值