当前位置 博文首页 > chixun5623的专栏:jvm - 垃圾回收

    chixun5623的专栏:jvm - 垃圾回收

    作者:[db:作者] 时间:2021-09-01 19:13

    jvm - 垃圾回收

    注意 : 本系列文章为学习系列,部分内容会取自相关书籍或者网络资源,在文章中间和末尾处会有标注

    垃圾回收的意义

    它使得java程序员不再时时刻刻的关注内存管理方面的工作.

    垃圾回收机制会自动的管理jvm内存空间,将那些已经不会被使用到了的"垃圾对象"清理掉",释放出更多的空间给其他对象使用.

    何为对象的引用?

    Java中的垃圾回收一般是在Java堆中进行,因为堆中几乎存放了Java中所有的对象实例

    在java中,对引用的概念简述如下(引用强度依次减弱) :

    • 强引用?: 这类引用是Java程序中最普遍的,只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象

    • 软引用?: 用来描述一些非必须的对象,在系统内存不够使用时,这类对象会被垃圾收集器回收,JDK提供了SoftReference类来实现软引用

    • 弱引用?: 用来描述一些非必须的对象,只要发生GC,无论但是内存是否够用,这类对象就会被垃圾收集器回收,JDK提供了WeakReference类来实现弱引用

    • 虚引用?: 与其他几种引用不同,它不影响对象的生命周期,如果这个对象是虚运用,则就跟没有引用一样,在任何时刻都可能会回收,JDK提供了PhantomReference类来实现虚引用

    如下为相关示例代码

    public class ReferenceDemo {
        public static void main(String[] arge) {
            //强引用
            Object object = new Object();
            Object[] objects = new Object[100];
    
            //软引用
            SoftReference<String> stringSoftReference = new SoftReference<>(new String("SoftReference"));
            System.out.println(stringSoftReference.get());
            System.gc();
            System.out.println(stringSoftReference.get()); //手动GC,这时内存充足,对象没有被回收
    
            System.out.println();
    
            //弱引用
            WeakReference<String> stringWeakReference = new WeakReference<>(new String("WeakReference"));
            System.out.println(stringWeakReference.get());
            System.gc();
            System.out.println(stringWeakReference.get()); //手动gc,这时,返回null,对象已经被回收
    
            System.out.println();
    
            //虚引用
            //虚引用主要用来跟踪对象被垃圾回收器回收的活动。
            //虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。
            //当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中
            ReferenceQueue<String> stringReferenceQueue = new ReferenceQueue<>();
            PhantomReference<String> stringPhantomReference = new PhantomReference<>(new String("PhantomReference"), stringReferenceQueue);
            System.out.println(stringPhantomReference.get());
        }
    }
    

    当然,关于这几种引用还有很多知识点,本文只做简单的介绍,后续有机会再单独的文章详细介绍.

    如何确定需要回收的垃圾对象?

    引用计数器

    每个对象都有一个引用计数器 , 新增一个引用的时候就+1,引用释放的时候就-1,当计数器为0的时候,就表示可以回收

    引用计数算法的实现简单,判定效率也很高,在大部分情况下它都是一个不错的选择,当Java语言并没有选择这种算法来进行垃圾回收,主要原因是它很难解决对象之间的相互循环引用问题

    public class LoopReferenceDemo {
        
        public static void main(String[] args) {
            TestA a = new TestA(); //1
            TestB b = new TestB(); //2
            a.b = b; //3
            b.a = a; //4
            a = null; //5
            b = null; //6
        }
        
    }
    
    class TestA {
        public TestB b;
    
    }
    
    class TestB {
        public TestA a;
    }
    

    虽然a和b都为null,但是a和b存在循环引用,这样a和b就永远不会被回收

    如果你在互联网上搜索"引用计数器"这个关键字,通常都会得到以上这一个结论,但是究竟为什么a和b不会被回,收其实还是没有说清楚的,下面简单说明一下 :

    • 第一行?: TestA的引用计数器加1,TestA的引用数量为1

    • 第二行?: TestB的引用计数器加1,TestB的引用数量为1

    • 第三行?: TestB的引用计数器加1,TestB的引用数量为2

    • 第四行?: TestA的引用计数器加1,TestA的引用数量为2

    内存分布如下图

    引用计数器示例-1

    • 第五行?: 将a变量设置为null,不再指向堆中的引用,所以TestA的引用计数器减1,TestA的引用数量为1

    • 第六行?: 将b变量设置为null,不再指向堆中的引用,所以TestB的引用计数器减1,TestB的引用数量为1

    内存分布如下图

    引用计数器示例-2

    • 结论?: 虽然上面程序将a和b设置为null了,但是在堆中,TestA和TestB还是互相持有对方的引用,引用计数器依然不等于0,这个就称为循环引用,所以说"引用计数器"会存在这个问题,导致这类对象无法被清理掉.

    以上的知识点参考 :?https://www.zhihu.com/question/21539353

    cs