深入理解Java虚拟机第三章读书笔记:垃圾回收算法

更正

之前写这篇笔记的时候我对标记-清除算法的理解好像有些偏差,最近复习笔记的时候发现之前描述的那个过程好像容易造成误解,因为是个人学习笔记,我就不在正文中修改了,方便自己以后回看。

我重新总结一下标记-清除算法的过程:

如果对象通过可达性分析后发现没有与GC Roots相连的引用链,那么它会被进行第一次标记,并且进行一次筛选(筛选的条件是有没有必要执行finalize()方法,这一部分下文中没有写错,可以参照),如果对象被判定为没有必要执行finalize()方法的话,那么它就会被放入一个“即将回收集合”中;如果对象被判定为有必要执行finalize方法,那么这个对象会被放置在一个F-Queue队列中,由JVM创建的一个低优先级的finalize线程来执行这些对象的finalize()方法。(这里所描述的执行,其实只是触发这个对象的finalize()方法,并不承诺等它执行完,因为如果一个对象的finalize()方法执行缓慢,或者发生了死循环,很可能会导致整个F-Queue队列的对象永久处于等待状态,甚至导致内存回收系统崩溃),如果有对象通过执行finalize()方法重新获得了引用,那么就会被移出“即将回收集合”,然后对还存在于“即将回收集合”中的对象进行第二次标记,标记过后就会开始清除工作。


1.引用计数法和可达性分析算法

Q1:垃圾收集器什么时候会回收一个对象呢?

A1:当一个对象不能再通过任何途径被引用的时候它就会被垃圾收集器收回

Q2:那么垃圾收集器是怎么判断一个对象是否还能够被引用的呢?

A2:大致有如下两种方法

  1. 引用计数法:给对象添加一个引用计数器,有任何一个地方对它进行引用的时候,就给这个计数器加一,有任何一个引用失效时,就将计数器减一。任何时刻计数器的值等于0,那么对象就不能再被引用。引用计数法的实现较为简单,且效率也比较高,大部分情况下都是一个不错的算法。微软的COM技术,Python语言等都在使用它。但是,在主流的Java虚拟机中并没有使用它,主要原因是它难以解决对象之间循环引用的问题。例如对象A和对象B都有instance字段,赋值令A.instance = B,B.instance = A.除此以外两个对象再无任何引用,但是这两个对象实际上已经无法再被引用,因为他们彼此引用着对方,但是他们的引用计数器都不为0,垃圾收集器也无法回收它们。再一个Java中的引用类型有多种,有的引用类型并不能实际上去使用牵引着的对象。而计数器无法区别这些引用类型,所以使用引用计数器法容易引起冲突。
  2. 可达性分析算法:这个算法的基本思想就是通过一系列的称为“GC Roots”的对象作为起始点‘’,然后从这些节点开始向下搜索,搜索走过的路径被称为引用链,当一个对象无法通过引用链到达GC Roots的时候,那么它就是不可再被引用的,例如下图,object5、6、7无法通过引用链到达GC Roots,那么它们就会被判定为可回收的对象。可以作为GC Roots的对象有以下几种:
  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Java Native Interface)引用的对象

2.强引用、软引用、弱引用、虚引用

  • 强引用          Object  O  =  new  Object(),这样的引用就是强引用。只要强引用还在,那么垃圾收集器永远不会收回这类对象
  • 软引用          软引用用来描述一些还有用但非必须的对象。对于软引用关联着的对象,在系统发生内存溢出异常之前,垃圾收集器就会回收被软引用关联着的对象,如果回收以后内存还是不够,系统才会抛出内存溢出异常。JDK1.2以后,提供了SoftReference类来实现软引用
  • 弱引用          弱引用用来描述非必须对象的,被弱引用关联着的对象,在下次垃圾收集发生之前,不管内存够或者不够,都会强制将这些对象回收。JDK1.2以后,提供了WeakReference类来实现弱引用
  • 虚引用          无法通过虚引用来获取一个对象实例,它的唯一作用是在这个被关联的对象被回收时,收到一个系统通知。JDK1.2y以后,提供了PhantomReference类来实现虚引用

 3.垃圾收集算法

3.1标记-清除算法

  • 第一次标记

如果对象再进行可达性分析算法分析后,发现与GC Roots没有引用链相连,那么就会被进行第一次标记,并进行一次筛选。

筛选的条件是该对象是否有必要执行finalize()方法,如果该对象finalize()方法没有被覆盖过,或者已经被虚拟机调用过了该方法,那么就会被判断为没有必要执行。(finalize()方法是Object的protected方法,子类可以覆盖该方法以进行资源清理工作,它不与C++中的析构函数对应,这个方法只能被执行一次,目的是为了防止对象无限复活

如果被判定为没有必要执行finalize()方法,那么就会被二次标记,进行回收。如果这个对象被判定为有必要执行finalize()方法,那么这个对象就会被放置到一个F-Queue队列中,之后会有一个由虚拟机自动建立的低优先级的Finalizer线程来执行这个队列中对象的finalize()方法。

  • 第二次标记

finalize()方法是对象逃脱死亡的最后一次机会,稍后GC将会进行第二次小规模的标记,如果对象要在finalize()中成功的拯救自己,即只要重新与任何一个对象建立关联,比如使用this关键字来讲自己赋值给某个对象的变量。那么在第二次标记时它将被移出{即将回收}这个集合。

如果这个时候对象还没有逃脱,那么它基本上就会被回收了。

标记-清除算法的缺点:

  • 效率低下,标记和清除两个过程的效率都不高
  • 空间问题,标记清除后会产生大量的内存空间碎片。空间碎片太多,会导致以后分配较大对象的时候,会没有足够连续可用的空间来分配给该对象,从而导致再一次触发垃圾收集动作

3.2复制收集算法

复制算法是对标记-清除算法的一种改进。

将内存划分为两块空间大小相等的区域,每次只使用其中的一块。当使用中的那块内存空间被消耗完的时候,就将此时还存活的对象复制下来,并移动到另外一块还没有被使用过的内存空间中。然后将使用过的那块内存一次性全部清理掉。这样就只需要对一半的内存空间进行清理,大大提高了效率,也避免了出现内存碎片这个问题。

复制算法的缺点:

  • 将内存空间分为两块,一次只使用一半,相当于浪费了一半的内存,内存使用率上有点过多的浪费

3.3标记-整理算法

复制收集算法在对象存活率较高时就要进行较多的复制操作,会使得效率变低,在老年代中就非常不适用。标记-整理算法是在标记-清除算法上的一种改进算法。标记过程一样,但是后续不是对可回收对象进行清理,而是让所有还存活的对象都向一端移动,然后把端界限以外的空间全部清理出来。

3.4分代收集算法(当前主流虚拟机都使用的垃圾收集策略)

根据对象存活周期的不同将内存划分为几块,一般都是把Java堆内存划分为“新生代”和“老年代”,然后根据新生代和老年代不同的特性来使用不同的垃圾收集算法。

例如新生代中的对象一般都是“朝生夕死”,存活率不高,每次都只有少量的对象存活,那么就使用复制收集算法来进行垃圾收集。只需要付出少量存活对象的复制成本就可以完成收集。

而老年代中的对象存活率很高,没有额外的空间能对它进行分配担保,就必须使用标记-清除算法或者标记-整理算法来进行回收。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值