深入理解JAVA虚拟机读书笔记(4)

    之前提到的了根搜索算法,它可以解决我们应该回收哪些对象的问题,但是它显然还不能承担垃圾搜集的重任,因为我们在程序(程序也就是指我们运行在JVM上的JAVA程序)运行期间如果想进行垃圾回收,就必须让GC线程与程序当中的线程互相配合,才能在不影响程序运行的前提下,顺利的将垃圾进行回收。

    为了达到这个目的,标记/清除算法就应运而生了。它的做法是当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被成为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。

    标记:标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。
    清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。

    其实这两个步骤并不是特别复杂,也很容易理解。用通俗的话解释一下标记/清除算法,就是当程序运行期间,若可以使用的内存被耗尽的时候,GC线程就会被触发并将程序暂停,随后将依旧存活的对象标记一遍,最终再将堆中所有没被标记的对象全部清除掉,接下来便让程序恢复运行。

    标记/清除算法之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其不足进行改进而得到的。 它的主要不足有两个:

        1:效率问题,标记和清除两个过程的效率都不高,要递归与全堆对象遍历,而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲;

        2:空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程中需要分配较大对象时,且JVM就不得不维持一个内存的空闲列表,这又是一种开销。在分配数组对象的时候,寻找连续的内存空间会不太好找。无法找到足够的连续内存而不得不提前触发另一次垃圾。

    下面介绍下由这个标记清除法演变过来的一些算法。

    1.复制算法:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。 当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。 这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

    但是复制算法的缺点也很明显:

        1、它浪费了一半的内存。

        2、如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可 忽视。所以从以上描述不难看出,复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的浪费。

     2.标记整理:它的做法和标记清除法很类型,其中标记做法一样,只是这里的整理是移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。

    标记整理算法缺点:

        1、效率不如复制算法,因为标记整理算法中,除了要标记存活的对象,还要整理所有存活对象的引用地址。

    3.分代算法(目前JVM所采用的算法):其实分代算法并不是什么新的算法,它是根据对象存活周期的不同将内存划分为几块。 一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法(就是前三种算法)。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。 而老年代中因为对象存活率高、 没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。



更好的参考博文:http://www.cnblogs.com/zuoxiaolong/p/jvm5.html

      

    原文作者:java虚拟机
    原文地址: https://blog.csdn.net/Tongzc721/article/details/79628480
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞