垃圾回收概念与算法

来源:互联网 发布:视频提取软件 编辑:程序博客网 时间:2024/06/04 23:30

1.引用计数法(Reference Counting)

1)        引用计数器的实现很简单,对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器加1,当引用失效时,引用计算器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用。

2)        引用计算器有两个非常严重的问题:

              a)        无法处理循环引用情况。

              b)        引用计算器要求在每一次因引用产生和消除的时候,需要伴随一个加法操作和减法操作对系统性能会有一定的影响。


3)        可到达对象:指通过根对象进行引用搜索,最终可到达的对象

           不可到达对象:通过根对象进行引用搜索,最终没有被引用到的对象

1.2 标记清除法(Mark-Sweep)

     标记清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,要标记阶段,首先通过根节点从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。标记清除算法可能产生最大问题是空间碎片。


1.3 复制算法(Copying)

(1).        复制算法的核心思想是:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存中,之后,清除正在使用的内存块中的所有对象,交换两个内存角色,完成垃圾回收。


(2).        在java的新生代串行垃圾回收器中,使用了复制算法的思想。新生代分为eden空间、from空间、和to空间3个部分。其中from和to空间可以视为用于复制的两块大小相同、地位相等、且可进行角色互换的空间。From和to空间也称为survivor空间,即幸存者空间,用于存放未被回收的对象。


(3).        新生代:存放年轻对象的堆空间。年轻对象指刚刚创建的,或者经历垃圾回收次数不多的对象。

            老年代:存放老年对象的堆空间。老年对象指经历多次垃圾回收依然存活的对象。

1.4标记压缩法(Mark-Compact)

     标记压缩算法也首先需要从节点开始,对所有可达到对象做一次标记。但之后,它并不只是简单地清理未标记的对象,而是将所有存活对象压缩到内存的一端。之后,清理边界所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比较高。


1.5 分代算法(Generational Collecting)

1.        分代算法就是基于这种思想,它将内存区间根据对象特点分成几块,根据每块内存区间的特点,使用不同的回收算法,以提高垃圾回收的效率。

2.        一般来说,Java虚拟机会将所有的新建对象都放入称之为新生代的内存区域,新生代的特点是对象朝生夕灭,大约90%的新建对象会被很快回收,因此,新生代比较适合使用复制算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老年代的内存空间。在老年代中,几乎所有的对象都是经过几次垃圾回收后依然得以存活的。因此,可以认为这些对象在一段时间期内,甚至在应用程序的整个生命周期中,将是常驻内存的。

3.        在极端情况下,老年代对象的存活率可以达到100%。如果依然使用复制算法回收老年代,将需要复制大量对象。再加上老年代的回收性价比也低于新生代,因此这种做法是不可取的。根据分代的思想,可以对老年代的回收使用与老年新生代不同的标记压缩或标记清除算法,以提高垃圾回收效率。



对于新生代和老年代来说,通常,新生代回收的频率很高,但是每次回收的耗时都很短,而老年代回收的频率比较低,但是会耗更多时间。为了支持高频率的新生代回收,虚拟机可能使用一种作卡表(Card Table)的数据结构。卡表为一个比特集合,每一个比特位可以用来表示老年代某一区域中的所有对象是否持有新生代的引用。这样在新生代GC时,可以不用花大量时间扫描所有老年对象,来确定每一个对象的引用关系,而可以先扫描卡表,只有当卡表的标记位为1时,才需要扫描给定区域的老年代对象,而表中位为0的所在区域的老年代对象,一定不含有新生代对象的引用


1.6 分区算法(Region)

     分代算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同小区间。每一个小区间独立使用,独立回收。这种算法好处可以控制一次回收多少个小区间。


2.判断可触及性

2.1对象的复活

1)        可触性的:从根节点开始,可以到达这个对象

2)        可复活的:对象的所有引用都被释放,但是对象有可能在finalize()函数中复活

3)        不可触及的:对象的finalize ()函数被调用,并且没有复活,那么就会进入不可触及状态,不可触及的对象不可能复活,因为finalize()函数只会被调用一次。


 

注:finalize()函数是一个非常糟糕的模式,再次不推荐读者使用finalize()函数释放资源。因为finalize()函数有可能发生引用外泄,在无意中复活对象;由于finalize()是被系统调用的,调用时是不明确的,因此不是一个好的资源释放方案,推荐在try-catch-finally语句中进行资源的释放。


2.2强引用——引用和可触及性的强度

1)        概念:强引用就是程序中一般使用的引用,强引用的对象是可触及的,不会被回收。

2)        特点:

       a)        强引用可以直接访问目标对象。

       b)        强引用所指向的对象在任何时候都不会被系统回收,虚拟机宁愿抛出OOM异常,也不会回收强引用所指向对象。

       c)        强引用可能导致内存泄漏。

2.3 软引用——可被回收的引用

软引用是比强引用弱一点的引用类型。一个对象只持有软引用,那么当堆空间不足时,就会被回收。软引用使用java.lang.ref.SoftReference类实现。GC未必会回收软引用对象,但是,当内存资源紧张时,软引用对象会被回收,所以软引用对象不会引起内存溢出。

2.4弱引用——发现即回收

弱引用是一种比软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统空间使用情况如何,都会将对象进行回收。由于垃圾回收器的线程通常优先级很低,因此,并不一定能很快地发现持有弱引用的对象。在这种情况下,弱引用对象可以存放在较长的时间。一旦一个弱引用对象被垃圾回收,便会加入一个注册的引用的队列中(这一点和软引用很像)。弱引用使用java.lang.ref.WeakReference类实现。

注:软引用、弱引用都非常适合来保存那些可有可无的缓存数据。如果这么做,当系统内存不足时,这些缓存数据回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。

 

2.5虚引用——对象回收跟踪

虚引用是所有引用类型中最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总是会失败,并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。当垃圾回收器准备回收一个对象,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序对象的回收情况。



0 0
原创粉丝点击