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虚拟机
    • 炼数成金
  • 相关阅读:
    U盘复制文件到最后5秒会卡住怎么办解决
    解决PuTTY中文乱码
    NSA Fuzzbunch中EternalRomance工具复现过程
    VirtualBox修改现有VDI虚拟磁盘大小
    高危Windows系统 SMB/RDP远程命令执行漏洞 手工修复办法
    数据库页已标记为 RestorePending,可能表明磁盘已损坏。要从此状态恢复,请执行还原操作。
    用Partimage创建或恢复分区备份
    IIS7保存配置文件及导入、导出、备份、还原
    WINDOWS SERVER 2008远程桌面端口修改方法
    谷歌启用抓取JavaScript,应对方案!
  • 原文地址:https://www.cnblogs.com/cloudflow/p/13894278.html
Copyright © 2011-2022 走看看