垃圾收集器——判定回收

     运行时数据区域除了按照线程私有公有划分以外,还可以按照内存分配是否具有确定性来进行一个分类。其中程序计数器、虚拟机栈和本地方法栈三个区域与线程生命周期相同。栈中的栈帧随着方法的进入和退出而有条不紊的执行者出栈和入栈操作。每个栈帧中分配多大的内存基本上在类结构确定下来时就是已知的了(尽管编译器会由JIT编译器进行一些优化),因此这几个区域的内存分配和回收都具备确定性,方法结束或者线程结束时,内存自然就被回收了。而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能是不一样的一个方法中的多个分支需要的内存也是不一样的,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,所以在讲五大运行时数据区域时,Java堆为垃圾回收器管理的主要区域。

一、堆区的内存回收

     1. 判断对象是否需要回收的两种方法

     在堆里面存放着Java中几乎所有对象的实例,垃圾收集器在对堆进行回收前,首先会判断哪些对象需要被回收,即不可能再被任何途径使用的对象。目前有两种判断对象是否需要回收的方法:

     ① 引用计数算法

     引用计数算法是给对象添加了一个引用计数器,每当有一个地方引用它时,计数器的值就加1,当引用失效时,计数器的值就减1。任何时刻计数器为0的对象就表示暂时不可能再被使用的对象。这种判断对象是否在被使用的方式虽然实现比较简单,判断效率也比较高,但在大多数主流的虚拟机是没有被采用的。其中最主要的原因是它很难解决对象之间的循环引用问题。例如objA和objB两个对象都有字段instance,赋值令objA.instance=objB及objB.instance=objB,就会造成两个对象的引用计数器的值均不为0,所以造成对象无法被回收。

     ②可达性分析算法

     在主流的商用程序语言,其实主要采用的是这种可达性分析算法来判断对象是否需要被回收。这个算法的基本思路就是通过一系列的“GC Roots”的对象作为起始点,对象的每一次正常引用实际上就对应着一个以“GC Roots”为头节点的挂链过程,相反引用失效时也就对应着一个断链的过程。当需要进行垃圾回收时,GC就会以“GC Roots”对象作为起始点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链时,也就是该算法所说的没有可达路径,则证明该对象是不可用的,也就是需要被回收的对象。

在Java语言中,GC Roots是一组必须活跃的引用。一般为全局性引用(常量、类静态属性)和执行上下文(栈帧中的本地变量表)。可作为GC Roots的对象包括以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象,也就是当前正在被调用的方法的引用类型的参数、局部变量以及临时值。
  • 方法区中类静态属性引用的对象,也就是指向当前被加载的类的引用。
  • 方法区常量引用的对象,也就是常量池的引用类型常量。
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

     2. dk1.2和jdk1.2之后版本的引用

     无论是采用引用计数算法还是可达性分析算法,判定对象存活都与“引用”有关。在jdk1.2之前,Java定义引用为reference类型的数据中存储另一个对象的起始地址的对象。这种定义相对狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态。但我们有时候希望一些对象在内存空间足够时保留,在进行垃圾回收之后还是非常紧张的时候摒弃。实际上在很多操作系统中的缓存功能都符合这种应用场景。所以在jdk1.2之后,Java对引用的概念进行了扩充,将其分为强引用、软引用、弱引用以及虚引用4种,这4中引用强度一次逐渐减弱。

  • 强引用指程序中普遍存在的,类似“Object obj=new Object() "这类的引用,只要强引用还在,垃圾收集器就永远不会回收掉被引用的对象;
  • 软引用是用来描述一些还有用但并非必须的对象。对于软引用关联的对象,在系统将要发生溢出异常之前,将会把这些对象列入回收范围之中准备进行第二次回收。如果两次回收完成后还没有足够的内存,才会抛出内存溢出异常。Java提供SoftReference类型来实现软引用;
  • 弱引用也是用来描述非必须对象的,但它的强度比软引用更软弱一些,被弱引用关联的对象只能生存到下一次垃圾回收发生之前。与软引用不同的是,弱引用不会考虑第一次垃圾回收后内存是否够用,也就是不管怎样,进行第二次回收时,弱引用都将被回收。Java中提供了WeakReference类来实现弱引用;
  • 虚引用为最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过一个虚引用来取得一个对象实例。而设置它的主要目的是能在这个对象被收集器回收时受到一个系统通知。Java提供了PhantomReference类来实现虚引用。

 

     3. 对象生存还是死亡的两次决断

     不管采用引用计数算法还是可达性分析算法,此时的对象都不能定性为必须回收不可。一个对象是否必须被回收需要进行两次标记过程,第一次标记就是对象在进行可达性或者引用计数分析之后确定对象为不可用对象,第二次标记是判断此对象是否有必要执行finalize()方法。当该对象没有覆盖finalize()或者finalize()方法已被虚拟机调用过,虚拟机将这两种情况均视为”没有必要执行“,这种情况下对象就直接宣布回收死亡。

     如果一个对象被判定为有必要执行finalize()方法时,此时finalize()方法就成为了该对象的”救命稻草“。那么这个对象将会被放置在一个F-Queue的队列中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它。这里的”执行“可能与其它的不同,它指的是虚拟机会触发这个方法,但不会等待它运行结束,这样做的原因就是如果一个对象在finalize方法中执行缓慢,或者发生死循环,将可能导致F-Qunue队列中其它对象永久处于等待状态,甚至导致整个内存回收系统崩溃。前面我们提到,执行finalize()方法是一个对象的”救命稻草“,在执行finalize()方法中,只要重新与引用链上的任何一个引用关联,那在第二次标记时该对象将会被移除”即将回收“的集合。相反,若finalize()方法未能拯救它,它也将直接宣布回收死亡。但必须注意的是,finalize()方法只能被该对象调用一次,也就是该对象被判定可回收之后,只有一次救命的机会。如以下代码示例:

测试结果如下:

     finalize()在很容易让人理解为C/C++中的析构函数,但其实不然。它只是Java刚刚诞生为了使C/C++程序员更容易理接受它所做出的一个妥协。在Java中一般建议尽量避免使用finalize方法,它的运行代价高昂,不确定性大,无法保证各个对象的调用顺序。

二、方法区的内存回收

     很多人认为方法区是没有垃圾收集的,特别是HotSpot虚拟机的永久代,对永久代进行垃圾收集的效率非常低。永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。回收废弃常量池与回收Java堆中的对象非常类似。以常量池中字面量的回收为例,例如一个字符串”abc“已经进入常量池中,但没有任何String对象引用常量池中的”abc“常量,也没有其它地方引用这个字面量,如果这时发生内存回收,而且必要的话,这个”abc“常量就会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。判断一个常量是否为”废弃常量“比较简单,而判定一个类是否是”无用的类“的条件则相对苛刻。

类需要同时满足下面三个条件才能算是”无用的类“:

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
  • 加载该类的ClassLoader已经被回收;
  • 该类对应的Java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

     满足上述3个条件的类只是被判定为可以被虚拟机回收,而不是和对象一样,两次判定完成之后就必然会回收。是否对类进行回收,还需要对虚拟机进行相应的参数设置。在HotSpot中,虚拟机提供-Xnoclassgc参数进行控制,还可以使用-verbose:class以及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类加载和卸载信息,其中-verbose:class和-XX:+TraceClassLoading可以在Product版的虚拟机中使用,-XX:+TraceClassUnLoading参数需要FastDebug版的虚拟机支持。在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGI这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载功能,以保证永久代不会溢出。

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值