zoukankan      html  css  js  c++  java
  • 深入JVM内核(三)对象存活判定算法与垃圾收集算法

    date: 2018-10-16 13:02:54

    对象存活判定算法

    引用计数法(Reference Counting)

    思路很简单,给每个对象中添加一个引用计数器,每当一个地方引用它时,计数器值加一;当引用失效时,计数器值减一。任何时候,当计数器值为0就不可能再被使用了。

    引用计数法实现简单,判断效率也高。但是主流的Java虚拟机里面没有使用引用计数法来管理内存。主要的原因在于它很难解决对象之间相互循环引用的问题。

    可达性分析算法(Reachability Analysis)

    算法思路:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始往下开始搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots之间没有任何引用链相连,则证明此对象时不可用的。

    可作为GC Roots对象包括以下几种:

    • 虚拟机栈(栈帧中的本地变量表)中的引用对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中JNI(即一般说的Native方法)引用的对象

    再谈引用

    如果一个对象分为被引用或者没有被引用两种状态有些狭隘。
    我们希望描述这样一类对象:当内存空间还足够时,则能保留再内存之中;如果内存空间再进行垃圾收集之后还是非常紧张,则可以抛弃这些对象。
    在JDK1.2后,对引用进行了扩充,将引用分为:强引用、软引用、弱引用、虚引用。

    • 强引用:只要强引用还在,垃圾收集器就永远不会回收掉被引用的对象。
    • 软引用:描述一些还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
    • 弱引用:描述一些非必需的对象。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
    • 虚引用:一个对象是否有虚引用的存在,完全不会对其生存周期产生影响,也无法通过虚引用来获取一个对象实例。它的唯一作用是这个对象在被垃圾收集器回收时收到一个系统通知。

    “缓刑”与finalize()方法

    即使在可达性分析算法中不可达的对象,也并不是“非死不可”,可能会处于“缓刑”阶段。
    要宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后没有与GC Roots相关联的引用链,那它将会被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当没有对象覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,这两种情况被视为没有必要执行finalize()方法。

    注意:任何一个对象的finalize()方法都只能被系统自动调用一次。

    实例:

    public class CanReliveObj {
        public static CanReliveObj obj;
    
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("CanReliveObj finalize called");
            obj=this;
        }
    
        @Override
        public String toString(){
            return "I am CanReliveObj";
        }
    
        public static void main(String[] args) throws InterruptedException{
            obj = new CanReliveObj();
            obj = null;   //可复活
            System.gc();
            Thread.sleep(1000);
            if(obj == null) {
                System.out.println("obj 是 null");
            } else {
                System.out.println("obj 可用");
            }
            System.out.println("第二次gc");
            obj = null;    //不可复活
            System.gc();
            Thread.sleep(1000);
            if(obj == null) {
                System.out.println("obj 是 null");
            } else {
                System.out.println("obj 可用");
            }
        }
    }
    

    输出:

    CanReliveObj finalize called
    obj 可用
    第二次gc
    obj 是 null
    

    可触及性

    可触及的:
    从根节点可以触及到这个对象

    可复活的:
    一旦所有引用被释放,就是可复活状态
    因为在finalize()中可能复活该对象

    不可触及的:
    在finalize()后,可能会进入不可触及状态
    不可触及的对象不可能复活
    可以回收

    垃圾收集(Garbage Collection)算法

    标记-清除算法(Mark-Sweep)

    算法分为“标记”和“清除”两个阶段。首先标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象。 标记过程就是判断对象生死的过程。

    标记清除算法

    缺点:

    • 标记和清除两个过程的效率都不高
    • 空间问题,清除后会产生大量不连续的内存碎片

    复制算法(Copying)

    将可用的内存容量划分为大小相等的两块,每次只使用其中的一块。
    当这块的内存使用完了,就将还存活的对象复制到另一块内存上,然后使用过的内存空间一次性清理。

    复制算法

    缺点:
    空间浪费,每次只能使用一半的内存空间。
    如果对象的存活率较高,就会有太多的复制操作,效率会降低。所以老年代不采用这种算法。

    改进的复制算法:
    将内存空间分为一块较大的Eden空间和两块较小的Survivor空间(幸存区)。
    每次使用Eden和其中一个Survivor。回收时,将Eden和Survivor中还存活着的对象一次性的复制到另一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。

    复制算法2

    标记整理算法(Mark-Compact)

    首先标记所有需要回收的对象,然后让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

    标记整理算法

    分代收集算法(Generational Collection)

    当前商业虚拟机的垃圾收集都采用的一种方法。
    思想是:根据对象存活周期的不同将内存划分为几块。一般是将Java堆分为新生代和老年代。

    在新生代中,每次垃圾回收时都会发现大批对象死去,只有少量存活,所以使用复制算法,只需要复制少量的存活对象就可以完成收集。

    在老年代中,对象存活率较高,使用标记清理或者标记整理算法较好。


    参考:

    • 周志明. 深入理解JVM虚拟机
    • 炼数成金
  • 相关阅读:
    Study Plan The TwentySecond Day
    Study Plan The Nineteenth Day
    Study Plan The TwentySeventh Day
    Study Plan The Twentieth Day
    Study Plan The TwentyFirst Day
    python实现进程的三种方式及其区别
    yum makecache
    JSONPath 表达式的使用
    oracle执行cmd的实现方法
    php daodb插入、更新与删除数据
  • 原文地址:https://www.cnblogs.com/cloudflow/p/13894278.html
Copyright © 2011-2022 走看看