手记

JVM学习笔记(4)---垃圾收集器

哪些内存需要回收?

垃圾收集(Garbage Collection,GC),即常说的GC。
尽管目前“自动化”时代,内存由JVM自动回收,但需要了解其回收机制。当出现内存溢出、泄漏问题时方便排查,当垃圾收集成为系统达到更高并发量的瓶颈时,方便监控调节
由于程序计数器、虚拟机栈、本地方法栈随线程生灭,且每个栈帧分配多少内存都是在类结构确定下来就已知,所以这几个区域的内存分配和回收都有确定性
而堆和方法区的内存分配是 动态 的,这里所讨论的GC即指 堆和方法区 的内存。

什么时候回收?

内存的回收时机主要在于确定 对象是否还通过任何途径被使用

引用计数算法(已经淘汰的算法)

引用计数算法是给每个对象添加一个引用计数器,每当一个地方引用它,计数器就加一;引用失效时,计数器就减一。
其特点是:算法很简单,判定效率高。但它不能解决相互引用的问题。
看如下示例代码:

public class ReferenceCountingGC {    public Object instance = null;    //这个成员属性单纯用于内存占用,方便查看是否回收
    private byte[] bigSize = new byte[2*1024*1024];    public static void testGC(){
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();

        objA.instance = objB;
        objB.instance = objA;        //清除引用
        objA = null;
        objB = null;        //GC 观察objA与objB能否被回收
        System.gc();
    }    public static void main(String[] args) {

      testGC();

    }
}

对象ObjA与ObjB互相引用,但再无其他引用。如果采用引用计数算法,GC就无法回收他们。
主流的JVM中,没有采用引用计数法,就是因为它无法解决对象间循环引用的问题。

运行上段代码,我们可以看到内存 4328k->644k 的变化,说明对ObjA和ObjB的内存进行了回收,也说明Java虚拟机中没有采用引用计数算法。

运行结果


可达性分析算法

主流商用程序语言(Java,C#等)中,都是通过 可达性分析(Rechability Analysis) 判断对象存活。

可达性分析


可达性分析是通过一系列"GC Roots"作为起始点,向下开始搜索,所经过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何链相连,则对象不可用。


Java中,可作为GC Roots的对象包括以下几种:

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

对象引用

判断对象的存活,无论是引用计数法计算数量,还是可达性分析法判断引用的可达性,都与 引用 有关。

引用可分为 4 类:

  • 强引用(Strong Reference):形如 Object obj = new Object(),只要引用在,GC不回收对象。

  • 软引用(SoftReference):内存溢出前,会进行二次回收,适合做缓存

  • 弱引用(WeakReference):下一次GC前,会直接进行回收

  • 虚引用(PhantomRefence):仅用于对象回收时发送系统通知


强引用
强引用的对象,不会被GC回收;删除强引用后,GC才会回收。
示例代码如下:

/**
 * 强引用:GC不会回收对象
 */public class StrongRef {    public static void main(String[] args) throws InterruptedException {
        Referred strong = new Referred();
        System.gc();
        Thread.sleep(2000);        //删去引用
        strong = null;
        System.out.println("删除强引用后");
        System.gc();
        Thread.sleep(2000);
    }    static class Referred {        @Override
        protected void finalize() throws Throwable {
            System.out.println("GC时引用对象被收集");
        }
    }
}

finalize()函数:如果一个对象覆盖了finalize()函数,则在对象被回收时,finalize()方法会被GC收集器触发。每个对象的finalize()函数只会被系统调用一次。

运行结果如下:

删除强引用后
GC时引用对象被收集

当强引用存在时,GC时对象没被回收;当强引用被删除,GC时对象被回收。


软引用
软引用的对象,在内存溢出前,会进行二次回收,这种特性非常适合做缓存。

示例代码如下:

/**
 * VM args: -Xmx100m -Xms100m
 *
 */public class SoftRef {    public static void main(String[] args) throws InterruptedException {

        SoftReference<Referred> soft = new SoftReference<Referred>(new Referred());

        System.gc();
        Thread.sleep(2000);

        System.out.println("开始堆占用");        try {
            List<SoftRef> heap = new ArrayList<>();            while (true) {
                heap.add(new SoftRef());
            }
        } catch (OutOfMemoryError e) {            // 软引用对象应该在这个之前被收集
            System.out.println("内存溢出");
        }
    }    static class Referred {        @Override
        protected void finalize() throws Throwable {
            System.out.println("GC时引用对象被收集");
        }
    }
}



作者:weberweber
链接:https://www.jianshu.com/p/cbe1b7412fe2


0人推荐
随时随地看视频
慕课网APP