java虚拟机-垃圾回收机制

     我们都知道Java使用的是垃圾自动回收机制,开发者无需像C程序员一样时时刻刻关心何时做垃圾回收。但是作为一名Java程序员了解JVM的垃圾回收机制是一件非常必要的事情,对于分析系统发生OM的问题,内存泄漏等问题时会非常有帮助。
     对于垃圾回收,我们比较关心的是以下几个问题:
          1、回收些什么玩意?
          2、什么时候回收?
          3、怎样回收?
 
一、回收什么玩意?
     Java是面向对象的语言,那么它的垃圾回收当然是回收那一个个被创建出来的对象实例。在了解垃圾回收机制之前建议先看看 JVM的内存管理,否则有些地方看起来会比较费劲。在JVM规范中的内存管理定义了本地方法栈,虚拟机栈,程序计数器,方法区,java堆,直接内存这几个主要的内存分配模块,其中本地方法栈,虚拟栈,程序计数器随着线程而生,随线程而灭,这几个地方基本不需要过多的考虑GC,因为随着线程的结束,他们所占用的内存自然就释放了,所以GC对象的主要战场就是方法区里存放的类信息,常量,静态变量和java堆里的对象实例。
 
二、什么时候回收?
     在java堆和方法区中存放的那些对象,JVM是怎么知道他们现在是没有用的垃圾了呢?这里就介绍两种判定方式,分别是引用计数算法和可达性分析算法
 
引用分析算法
     给对象添加一个引用计数器,当有其它对象引用它的时候,引用值+1,当引用失效的时候,引用值-1,客观的说引用分析算法确实是一个简单有效的算法,在游戏脚本领域有广泛的使用,但是在Java虚拟机里却没有采用它,原因是它很难解决对象之间互相引用的情况。如下图:
在整个程序中只有它们自己互相引用,并没有其他的地方需要用到它两,从实际角度来说它们属于垃圾对象,但是通过引用计数算法得出的结果它们又不是垃圾,并不会被GC掉,如果这样的垃圾对象一直堆积最终会导致OOM,所以java虚拟机采用的是另外一种算法
 
可达性分析算法
     从一系列的被称为“GC ROOT”对象出发,从这些节点开始向下搜索,搜索所走过的路经称为引用链,当一个对象到GC ROOT上没有任何的引用链时,就判断它为可被回收的垃圾对象。如下图所示:
Object4和Object5就是没有引用链的对象,也就意味着它们将被GC。可怜的小对象 吐舌头.
JDK把对象的引用进行了分类,分为强引用,软引用,弱引用,虚引用。下面分别来看看这些引用的区别
     强引用:类似Object1 ob = new Object1(); 这样的引用关系就是强引用,将永远不会被GC
     软引用:描述一些还有用,但是非必需的对象,这些对象在系统即将发生内存溢出异常之前会把他们加入秋后问斩的名单,如果把他们回收后还没有足够的内存,那么就会抛出OutofMemory的异常。
     弱引用:描述一些非必需的对象,当垃圾收集工作器工作的时候,无论当前系统内存是否足够,他们都会被斩首,一群的可怜的娃
     虚引用:最弱的一种引用关系,为一个对象设置虚引用的唯一目的就是在该对象被GC收到一个系统通知。一种累觉不爱的引用关系。
三、如何回收
     通常一个对象要真正的被回收需要经过两次标记。如下图:
每一个对象都继承自Object,Object里有一个finalize()方法,当JVM发现某个对象不可达的时候就会把它标记为可被回收的对象,也就是第一次标记。然后对这些对象进行一次筛选,条件就是是否有必要执行它的finalize()方法。如果没有就直接被干掉,如果有就将他们放入一个叫F-Queue的队列,然后一个虚拟机自动创建一个低优先级的线程去执行他们的finalize()方法,稍后对F-Queue里的对象进行二次标记,把那些执行完还没有引用链的对象干掉。
 
这里值得注意的是虚拟机并不会等F-Queue里的对象全部执行完finalize(),因为如果某个对象的finalize方法里有死循环,则GC将一直处于等待状态,导致整个内存回收系统崩溃。其次是判断是否有必要执行finalize()的方法是看它是已经执行过一次,或者对象没有覆写finalize()方法的都被认为没必要的而被直接被干掉。
 
方法区的回收
     有人认为方法区是永久代,是没有GC的,的确对方法区进行GC的性价比确实不高,但它还是有GC的。对堆中的新生代进行GC基本能挽回70~90%的内存空间,而对永久代的回收就达不到这么高的比例。
     方法区中的回收对象主要有两类:废弃常量和无用类。判断方法区中的常量池中的常量是否为废弃常量只需要判断是否有引用即可,判断无用类则是根据以下三个方面:
          a、java堆中没有该类的实例,也就是该类的实例全部被回收
          b、加载该类的ClassLoader已经被回收
          c、该类对应的java.lang.Class对象没有地方被引用,无法在任何地方通过反射访问
 
在大量使用反射,动态代理,CGLib等技术的地方都需要虚拟机具有加载类和卸载类的能力,以保证永久代不会内存溢出。

猜你喜欢

转载自liuxiamai.iteye.com/blog/2326076