深入虚拟机二(垃圾收集算法和收集器)

垃圾收集算法

标记-清除算法

    首先标记所有需要回收的对象,在标记完成后统一回收所有被标注的对象。标记就是前一章所说的(1-引用计数法 2-可达性分析法)。他是最基础的算法,后续的算法都是根据这种思路并对其不足改进而得到的。缺点:
1- 效率太低(标记和清除两个过程效率都不高)
2- 空间问题:标记清除后会导致大量不连续的空间碎片,碎片太多会导致在分配大对象时无法找到足够的空间而不得不提前触发一次gc

在这里插入图片描述

复制算法

    对内存划分为两个区域,每次使用一块。当一块内存用完了就将还存活的复制到另一块,把已使用的内存空间一次性清理掉。这样解决了空间碎片问题,因为只要移动堆顶指针,按顺序分配内存即可。但这样是以缩小内存为代价因为新生代的对象98%都是‘朝生夕死’的,Hostpot虚拟机默认eden和survivor的比例是8:1。这样只有10%的空间会浪费。当然我们没有办法保证每次回收只有不都10%的对象存活,当survivor空间不够用的时候,就需要老年代进行分配担保。
在这里插入图片描述

标记-整理算法

    因为新生代存活率很低所以适合复制算法,但老年代的存活率很高如果还是用那种算法会导致大量的内存浪费。所以根据老年代,基本上是使用“标记-整理”。过程和标记-清除算法一样,但后续步骤不是直接对可回收的对象进行清理,所有存活的对象都移向一端,然后清除端边界以外的内存
在这里插入图片描述

分代收集算法

    商业虚拟机都是采用该算法,这个算法也不是什么新思想。根据内存的存活周期不同将内存划分为几块。一般堆分为新生代和老年代,根据各个年代采用上面所讲的最适当的算法

hotspot的算法实现

  • 枚举根节点 更多

        GC Roots的一个枚举,可作为GC Roots的节点。确保一致性的快照(在整个分析期间执行系统看起来就像被冻结在某个时间点,不可以在分析过程中对象引用关系还在不断的变化)。因为为了保证一致性会导致GC必须停顿所有java执行线程(“Stop The World”)。当系统停顿后并不需要一个不漏的检查完所有执行上下文和全局的引用位置。虚拟机应该有办法直接知道哪些方法 存放着对象引用
    在HotSpot中,使用一组OopMap的数据结构来标记对象引用的位置。
    1- 在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来。
    2- 在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。
    3- 在OopMap的协助下,HotSpot可以快速且准确地完成GC Roots枚举。

  • 安全点

        在OopMap的协助下可以快速准确的完成GC Root枚举。但是OopMap内容变化的指令非常多,如果为每一个指令都生成对应的OopMap,将会需要大量的额外空间,这样GC的空间成本也会非常高。实际上HotSpot的确,没有为每一条指令都去生成OopMap,他只是会在"特定的位置"记录这些信息,这些位置称为安全点。程序只有到达安全点才能暂停开始GC.。所以safepoint既不能太少以至于让GC等待的时间太长,过多会增大运行时负荷
        safeppoint选定标准:是否具有让程序长时间执行的特征
        特征:指令序列的复用,例如方法调用、循环跳转、异常跳转等
    对于safeppoint需要考虑如何在GC发生时让所有的线程都跑到最近的安全点停顿,这里有两种方案可供选择:抢断式中断(Preemptive Suspension)和主动式中断(Voluntary Suspension)


抢断式中断:不需要执行代码的主动配合,首先把所有的线程全部中断,如果发现有线程中断的地方不再安全点,就恢复线程让他跑到安全点。现在几乎没有虚拟机采用该方式中断线程从而响应GC事件
主动式中断:当GC需要中断线程之后,不直接对线程操作,仅仅简单地设置一个标志,各各线程执行的时候主动去轮询这个标志,发现中断标志位真时就自己中断挂起。轮询标志的地方和安全点是重合的,另外在加上创建对象需要分配内存的地方。

  • 安全区域

    safeppoint似乎已经完美的解决了如何进入GC的问题,但实际情况却不一定,safeppoint机制保证程序时,在不太长的时间内就会遇到可进入GC的safepoint。但是程序不执行的时候?不执行就是执行的时候 没有分配CPU时间,典型的例子就是线程处于Sleep状态或者Blocked状态。这是的线程无法响应JVM的中断请求走到安全的地方去中断挂起。JVM显然也不可能等待线程重新被分配CPU时间。这种情况就需要安全区域(Safe Region) 来解决安全是指一段代码片段之中,引用关系不会发生变化。这个区域任意地方开始GC都是安全。我们可以把Safe Region看做是被扩展了的safepoint。在线程执行到Safe Region中的代码时首先会将自己标识,那样当JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了。在线程要离开时,他会检查系统是否完成了根节点枚举(也就是整个GC过程),如果完成了就继续执行,否则他就必须等待直到收到可以安全离开Safe Region的信号为止。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章