• JVM垃圾回收算法


    1 什么时候回收垃圾?

    1、什么场景下该使用什么垃圾回收策略?

    在对内存要求苛刻的场景:想办法提高对象的回收效率,多回收掉一些对象,腾出更多内存。

    在CPU使用率高的情况下:降低高并发时垃圾回收的频率,让CPU更多的去执行你的业务而不是垃圾回收。

    2、垃圾回收发生在哪些区域?

    堆(回收对象)、方法区(不用的常量、类)

    3、对象什么时候被回收?

    引用计数法:通过对象的引用计数器来判断对象是否被引用。无法处理循环引用问题。

    A --> B --> C --> D --> B 此时B被引用了两次
    当A不再引用B
    A     B --> C --> D --> B BCD循环引用无法被回收。
    

    可达性分析法:以根对象(GC Roots)为起点向下搜索,形成引用链。如果对象不在引用链上,便视作对象不可达,可以回收。

    image

    哪些对象可以作为GC Roots对象?

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

    注意的是,一个对象不可达,也不一定会被回收。

    image

    public class GCTest {
        private static GCTest obj;
    
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("finalize方法被调用了");
            obj = this; // 重新引用对象,从FQ队列中出队
        }
    
        public static void main(String[] args) throws InterruptedException {
            obj = new GCTest();
            obj = null;
            System.gc();
    
            TimeUnit.MILLISECONDS.sleep(1000);
            extracted();
    
            TimeUnit.MILLISECONDS.sleep(1000);
            obj = null; // finalize()方法已被调用过,对象会被直接回收
            System.gc();
            extracted();
        }
    
        private static void extracted() {
            if (obj == null) {
                System.out.println("obj == null");
            } else {
                System.out.println("obj可用");
            }
        }
    }
    finalize方法被调用了
    obj可用
    obj == null
    

    finalize()的建议

    • 避免使用finalize()方法,操作不当可能会导致问题。上面的例子中,第二次回收时如果不设置obj==null,对象很难被回收掉。

    • finalize()优先级低,何时会被调用无法确定,因为什么时间发生GC不确定。

    • 建议使用try...catch...finally来替代finalize()。

    4、四种引用

    强引用,例如new对象,永远不会回收对象。

    软引用,描述有用但是非必需对象,只有内存不足时被回收。

    弱引用,描述非必需对象,无论内存情况都会被回收。

    虚引用,主要用来跟踪对象被垃圾回收器回收的活动,结合引用队列使用。不影响对象的生命周期。

    Object obj = new Object();
    SoftReference<String> sr = new SoftReference<>("helllo");
    WeakReference<String> sr = new WeakReference<>("helllo");
    ReferenceQueue< String> queue = new ReferenceQueue<>();
    PhantomReference< String> pr= new PhantomReference<>(“hello“queue)
    

    2 垃圾回收算法

    垃圾回收算法

    基础垃圾回收算法

    • 标记清除法(Mark-Sweep):标记后直接删除。

      • 容易产生内存碎片;分配大内存时速度受到影响,需要找到适合它大小的空间。
    • 标记整理法(Mark-Compact):标记,内存整理到一侧,删除三步。

      • 整理存在开销,没有碎片。
    • 复制(Copy):内存分为两块,每次只使用其中一块;将存活对象复制到另一块内存,然后清空当前内存;交换使用另一块内存。

      • 内存利用率低,性能好,没有碎片。

    综合垃圾回收算法

    • 分代回收算法:把内存分为多个区域,不同区域使用不同的回收算法。
    • 增量算法:每次只收集一小块内存区域的垃圾。

    分代收集算法

    商业虚拟机普遍采用分代收集算法,根据对象的存活周期,把内存分为多个区域,不同区域使用不同的回收算法。

    分代的好处是更有效的清除不再需要的对象,提升垃圾回收的效率。

    • 新生代回收(Minor GC | Young GC)
    • 老年代回收(Major GC),通常伴随Full GC,Major GC≈ Full GC
    • 清理整个堆(Full GC)

    典型的对象分配流程如下。
    image

    新生代采用的是复制算法,存放存活周期短的对象。老年代对象存活时间长,采用标记-清除或标记-整理算法。

    堆的年轻代和老年代,内存比例1:2。年轻代分为Eden和Survivor,内存比例8:1:1。
    1. 对象首先存放到Eden中,当Eden内存满后,垃圾收集线程执行minor gc,通过GC Root进行可达性分析,找到非垃圾对象,复制到Survivor S0区(对象头中分代年龄标记为1),清空Eden区域,Eden又可以存储新对象。
    2. 当Eden再次满后,同时对Eden和S0执行minor gc,将非垃圾对象复制到S1(分代年龄加1),清空Eden和S0。S0和S1,同一时间只有一个被使用。
    3. 当分代年龄达到15后,对象会被复制到老年代。
    4. 当老年代满了,会对年轻代和老年代执行full GC。full GC不起作用,则会OOM。
    

    命令行输入jvisualvm,打开Java VisualVM工具的Visual GC插件可以看到程序执行过程中堆容量变化。每次Eden满进行GC,调整堆区域。

    image

    非典型的情况:

    1. 新建的对象不一定分配到伊甸园。
    • 对象大于-XX:PretenureSizeThreshold,就会直接分配到老年代

    • 新生代空间不够。例如大数组。

    1. 对象也不一定要达到年龄才进入老年代

    动态年龄:如果Survivor空间中所有相同年龄对象大小的总和大于Survivor空间的一半,那么年龄大于等于该年龄的对象就可以直接进入老年代。

    触发垃圾回收的条件

    Minor GC触发条件:伊甸园空间不足

    Full GC触发条件:

    1. 老年代空间不足(老年代已满;空间碎片太多,没有连续内存分配对象)
    2. 元空间不足
    3. 要转成老年代对象所需的空间大于老年代剩余空间。
    4. 显示调用System.gc(),建议垃圾回收器执行垃圾回收。设置-XX:+DisableExplicitGC 参数忽略该操作。
    分代收集调优思路
    • 让GC尽量发生在新生代,尽量减少Full GC的发生。
    • 合理设置Survior区域的大小,避免内存浪费。(复制算法)
    版权声明:本文为博主原创文章,未经博主允许不得转载。
  • 相关阅读:
    构建之法阅读笔记03
    构建之法阅读笔记02
    构建之法阅读笔记01
    人月神话阅读笔记03
    人月神话阅读笔记02
    人月神话阅读笔记01
    关于APP“跑跑”
    软件设计模式24
    软件构造9
    软件构造8
  • 原文地址:https://www.cnblogs.com/dtyy/p/15743347.html
Copyright © 2020-2023  润新知