深入JVM-垃圾收集器之内存回收

1.前言

在JVM内存运行时数据区中程序计数器、虚拟机栈、本地方法栈等三个区域是线程私有的,即线程结束对应的内存就会回收。而方法区和堆则是线程公有的,这部分内存只有在在程序运行期间才会知道创建哪些对象,这部分内存是分配和回收都是动态的,而垃圾收集器所关注的就是这部分内存。

2.判断对象是否可回收?

堆中存放的几乎都是对象实例,在垃圾收集器对堆回收前需要先判断对象是否已经能够回收。
判断对象是否可回收有两种算法:

  • 引用计数算法(java未使用)
  • 根搜索算法

2.1.引用计数算法

定义
         给对象中添加一个引用计数器,每当有一个地方引用时就给计数器值加1,每当引用失效时就给计数器减1,任何时刻计数器都为0的对象就是不可能被引用的,即对象可回收。
缺点: 无法处理相互引用的对象之间的关系。

2.2.根搜索算法

定义
         通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所有走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,即可回收的对象。
可以作为GC Roots的对象有如下几种:

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

2.3.java是否使用引用计数算法?

测试代码如下:

package com.glt.gc;

/**
 * 测试java是否使用引用计数算法
 */
public class ReferenceCounting {

    public Object instance = null;

    private static final int _1M = 1024 * 1024;

    private byte[] bigSize = new byte[2 * _1M];

    public static void main(String[] args) {
        ReferenceCounting objA = new ReferenceCounting();
        ReferenceCounting objB = new ReferenceCounting();

        objA.instance = objB;
        objB.instance = objA;

        objA = null;
        objB = null;

        System.gc();
    }
}

输入如下:
在这里插入图片描述
测试代码中使用两个对象相互引用,除此之外这两个对象都无其他引用,因为它们相互引用,导致他们的引用计数都不为0,引用计数算法就无法通知GC收集器回收它们。但是从运行结果中看到标红的部分,虚拟机还是进行了GC,即说明虚拟机并没有使用引用计数算法来判断对象是否存活。

2.4.内存回收之引用类型

java中有一类对象:当内存空间足够时,则能保存在内存中,如果内存在垃圾回收后还非常紧张,就可以抛弃这些对象。为了描述这些对象,java对象引用类型分为四种,强度依次减弱:

  • 强引用(Strong Reference)
            强引用类似“Object obj = new Object()”,只要引用还在,永远不会被回收
  • 软引用(Soft Reference)
            软引用用来描述一些还有用但是非必须的对象,系统会在将要发生内存溢出之前把软引用的对象列进回收范围内进行第二次回收。如果回收后还没有足够的内存,才会抛出内存溢出异常。
  • 弱引用(Weak Reference)
            若引用也是用来描述非必须对象的,强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉此类对象。
  • 虚引用(Phantom Reference)
            虚引用也称为幽灵引用或者幻影引用,是最弱的一种引用关系。一个对象是否有虚引用存在,完全不会对其生存时间造成影响,也无法通过虚引用获取对象实例。为一个对象设置虚引用关联只是为了能在这个对象呗收集器回收时收到一个系统通知

2.5.内存回收之finalize

根搜索算法中不可达的对象,在被回收前会经历两次被标记的过程:

  • 如果对象在进行根搜索后发现没有与GC Roots相连的引用链,那么它将会被第一次标记并且进行一次筛选,筛选条件是此对象对否有必要执行finalize()方法。如果对象没有覆写finalize()方法,或者finalize()方法已经调用过,则这种情况不会再执行finalize()方法。
  • 如果对象被判定为有必要执行finalize()方法,那么这个对象将被放置在一个名为F-Queue的队列中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行(使用线程是为了防止对象在执行finalize()时,执行太慢或者发生死锁等占用大量时间导致内存回收系统崩溃)。稍后GC将对F-Queue队列进行第二次小规模标记,如果对象在finalize()方法中重新与引用链关联上,则第二次标记时它将被移出“即将回收”的集合,不会被回收掉。

2.6.测试finalize()方法

package com.glt.gc;

/**
 * finalize()测试
 */
public class FinalizeGC {

    public static FinalizeGC inst = null;

    public void isAlive() {
        System.out.println("this is alive");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize is exe! ");
        inst = this;
    }

    public static void main(String[] args) throws Exception {
        inst = new FinalizeGC();


        inst = null;
        System.gc();
        //finalize优先级低,此处等待0.5秒
        Thread.sleep(500);
        //此处inst不为null,并且finalize方法执行了
        if(inst != null){
            inst.isAlive();
        }else{
            System.out.println(" this is dead");
        }


        inst = null;
        System.gc();
        //finalize优先级低,此处等待0.5秒
        Thread.sleep(500);
        //此处inst为null,并且finalize方法未执行
        if(inst != null){
            inst.isAlive();
        }else{
            System.out.println(" this is dead");
        }
    }
}

输出如下:

finalize is exe!
this is alive
this is dead

以上结果中两段代码段完全一样,执行结果却是一次存活,一次死亡,证明了以下两点:

  1. 对象可以在GC时自我拯救。
  2. 这种自救机会只有一次,因为一个对象的finalize()方法最多只会被系统调用一次。

2.7.回收方法区(永久代)

在堆的新生代中,常规应用进行一次垃圾收集,能收集70%-95%空间,而永久代的垃圾收集效率远低于此。
永久代中的垃圾回收主要分为两部分:废弃常量和无用的类。

  • 废弃常量
            如果一个常量已经进入了常量池,但是没有任何地方引用,则此为废弃常量,如果发生内存回收此常量会被回收掉。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
  • 无用的类
    无用的类需要满足下面三个条件:
            1.该类的所有实例都已经被回收;
            2.加载类的ClassLoader已经被回收;
            3.该类对应的java.lang.Class对象没有在任何地方呗引用,无法在任何地方通过反射访问该类的方法。
    虚拟机可以选择对满足上面三个条件的类是否进行回收,HotSpot虚拟机可以通过-Xnoclassgc参数进行控制。
    JVM查看类加载和卸载信息的相关参数如下
    HotSpot虚拟
            -verbose:class
            -XX:+TraceClassLoading
            -XX:+TraceClassUnLoading
    Product版虚拟机
            -verbose:class
            -XX:+TraceClassLoading
    fastdebug版虚拟机
            -XX:+TraceClassUnLoading



参考资料:
《深入理解JAVA虚拟机》

发布了61 篇原创文章 · 获赞 85 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/bluuusea/article/details/89483293