zoukankan      html  css  js  c++  java
  • 《深入理解 Java 虚拟机》学习 -- 垃圾回收算法

    《深入理解 Java 虚拟机》学习 -- 垃圾回收算法

    1. 说明

    • 程序计数器,虚拟机栈,本地方法栈三个区域随线程而生,随线程而灭,这几个区域的内存分配和回收都具备确定性
    • Java 堆和方法区这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存
    新生代和老年代

    Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。
    在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。
    堆的内存模型大致为:

    • 堆大小 = 新生代 + 老年代。

    • 垃圾回收一般发生在 java 堆中。

    • 方法区称作永久代。


    新生代:

    对象存活率低。

    老年代:

    对象存活率高。


    2. 判断堆中对象是否已死(不可能再被任何途径使用的对象)算法

    2.1 引用计数算法

    含义:

    给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加 1;当引用失效时,计数器就减 1;任何时刻计数器都为 0 的对象就是不可能再被使用的。

    缺点:

    很难解决对象之间的相互循环引用的问题。

    举例:

    下面例子中:对象 objA 和对象 objB 都有字段 instance,赋值令 objA.instance = objBobjB.instance = objA, 除此之外,这两个对象无任何引用,实际上这两个对象已经不可能再被访问,但是因为它们互相引用着对方,导致它们的引用计数都不为 0,于是引用计数算法无法通过 GC 收集器回收它们。

    public class ReferenceCountingGC {
    
        public Object instance = null;
    
        private static final int _1MB = 1024 * 1024;
    
        // 这个成员属性的唯一意义就是占点内存,以便能在GC 日志中看清楚是否被回收过
        private byte[] bigSize = new byte[2 * _1MB];
    
        public static void main(String[] args) {
            ReferenceCountingGC objA = new ReferenceCountingGC();
            ReferenceCountingGC objB = new ReferenceCountingGC();
            objA.instance = objB;
            objB.instance = objA;
    
            objA = null;
            objB = null
    
            // 假设在这行发生 GC,那么 objA 和 objB 是否能被回收
            System.gc();
        }
    
    }
    

    注意:VM options 配置参数:-verbose:gc -XX:+PrintGCDetails

    打印部分结果如下:

    [GC (System.gc()) [PSYoungGen: 6772K->680K(38400K)] 6772K->680K(125952K), 0.0098335 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    [Full GC (System.gc()) [PSYoungGen: 680K->0K(38400K)] [ParOldGen: 0K->623K(87552K)] 680K->623K(125952K), [Metaspace: 3213K->3213K(1056768K)], 0.0145286 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
    

    结果中可以看到 GC 日志中包含 6772K->680K,这意味这虚拟机并没有因为这两个对象互相引用就不回收它们,说明虚拟机并不是通过引用计数算法来判断对象是否存活的。

    2.2. 根搜索算法(可达性分析算法)

    含义:

    通过一系列的名为 “GC Roots" 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连(用图论的话来说就是从 GC Roots 到这个对象不可达)时,则证明此对象时不可用的。

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

    (方法区和栈中)

    举例:

    对象 object5,object6,object7 虽然互相有关联,但是它们到 GC Roots 是不可达的,所以它们将会被判定为是可回收的对象。


    3. 垃圾收集算法

    3.1 标记 - 清除算法

    含义:

    首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

    缺点:
    1. 效率不高
    2. 空间碎片太多
    示例:

    3.2 复制算法(一般在新生代中使用)

    含义:

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

    缺点:

    将内存缩小为原来的一半,代价太高。

    改进:

    将 内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中的一块 Survivor 。当回收时,将 Eden 和 Survivor 中还存活着的对象一次性地拷贝到另外一块 Survivor 空间上,最后清理掉 Eden 和刚才用过的 Survivor 的空间。

    示例:

    3.3 标记 - 整理算法(一般在老年代中使用)

    含义:

    让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

    示例:

    3.4 分代收集算法

    含义:

    根据对象的存活周期的不同将内存划分为几块,一般是把 Java 堆分为新生代和老年代,然后根据各个年代的特点采用最适当的收集算法。

    • 新生代:复制算法
    • 老年代:标记 - 整理 / 标记 - 整理

    现在基本都使用这种。

  • 相关阅读:
    杜教筛刷题总结
    后缀自动机刷题总结
    回文自动机刷题总结
    后缀数组刷题总结
    LCT刷题总结
    省选模拟一题解
    FFT/NTT中档题总结
    二项式反演总结
    JS只能输入数字,数字和字母等的正则表达式
    jquery 条件搜索某个标签下的子标签
  • 原文地址:https://www.cnblogs.com/weixuqin/p/11399217.html
Copyright © 2011-2022 走看看