美文网首页
垃圾收集器与内存分配策略(一)——两种判断对象已死的方法

垃圾收集器与内存分配策略(一)——两种判断对象已死的方法

作者: 路远处幽 | 来源:发表于2019-07-14 09:29 被阅读0次

在堆中存放着Java世界中几乎所有的对象实例,垃圾收集器在回收前,第一件事情是判断这些对象哪些是已经死去,可以回收的。判断的方式主要有两种:引用计数法和可读性分析。

一、引用计数算法

原理:给对象添加一个引用计数器,当有地方引用它时,计算器就加1,当引用失效时,计数器减1。任何计数器为0的对象,被判断为不可能再被使用。
优点:实现简单,判断效率很高,在大部分情况下都是一个不错的算法。例如微软的COM、ActionScript3的FlashPlayer、Python语言以及在游戏领域被广泛应用的Squirrel都使用了引用计数算法进行内存管理。
缺点:难以解决对象之间互相循环引用问题。也因此,在主流Java虚拟机中,都没有使用该算法来进行内存管理。

二、可达性分析算法

原理:这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(也就是GC Roots到这个对象不可达),则证明此对象是不可用的。主流的商用程序语言(Java、C#、古老的Lisp),都使用该算法来判断对象是否存活的。

image.png

如下代码:

public class ReferenceGCTest {
    
    public static final int _1MB = 1024 * 1024;
    
    public static void main(String[] args) {
        testGC();
    }
    
    public static void testGC() {
        TestE object5 = new TestE();
        TestF object6 = new TestF();
        object5.object6 = object6;
        object6.object5 = object5;
        
        System.out.println("对象存活情况1:");
        System.out.println(object5);
        System.out.println(object6);
        
        object5 = null;
        object6 = null;
        
        
        TestA object1 = new ReferenceGCTest.TestA();
        object1.object2 = new ReferenceGCTest.TestB();
        object1.object3 = new ReferenceGCTest.TestC();
        object1.object3.object4 = new ReferenceGCTest.TestD();
        
        try {
            Thread.sleep(1*1000);
        }catch(Exception e) {
            
        }
        
        System.out.println("对象存活情况2:");
        System.out.println(object1);
        System.out.println(object1.object2);
        System.out.println(object1.object3);
        System.out.println(object1.object3.object4);
        System.out.println(object5);
        System.out.println(object6);
        
        System.out.println(object1.object3.object4.bigSize.length);
        
    }
    
    
    //object1
    public static class TestA{
        public TestB object2 = null;
        public TestC object3 = null;
        
        public String toString() {
            return "object1 alive";
        }
        
    }
    
    //object2
    public static class TestB{
        private byte[] bigSize = new byte[10 * _1MB];
        
        public String toString() {
            return "object2 alive";
        }
    }
    
    //object3
    public static  class TestC{
        public TestD object4 =null;
        
        public String toString() {
            return "object3 alive";
        }
    }
    
    //object4
    public static class TestD{
        public byte[] bigSize = new byte[10 * _1MB];
        
        public String toString() {
            return "object4 alive";
        }
    }
    
    //object5
    public static class TestE{
        public TestF object6 = null;
        private byte[] bigSize = new byte[20 * _1MB];
        
        public String toString() {
            return "object5 alive";
        }
    }
    
    //object6
    public static class TestF{
        public TestE object5 = null;
        private byte[] bigSize = new byte[20 * _1MB];
        
        public String toString() {
            return "object6 alive";
        }
    }

}

如果按照引用计数法,object5和object6因为相互引用而无法被回收,实际上java虚拟机使用的可达性分析算法,两个对象是可以被回收的,可以在启动程序时加上内存参数(不能太大造成不会进行gc操作,也不能太小造成OOM异常),同时加上打印gc信息参数,查看详细信息:
-Xms100m -Xmx100m -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:./gclogs
程序运行结果:

对象存活情况1:
object5 alive
object6 alive
对象存活情况2:
object1 alive
object2 alive
object3 alive
object4 alive
null
null
10485760

image.png

Java中可作为GC Roots对象的有以下几种:

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

三、引用的几种类型

无论是通过引用计数算法还是可达性分析算法,判断对象的存活都与“引用”有关。在JDK1.2以前,对象只有被引用和没有被引用两种状态,JDK1.2之后,对引用概念进行了扩充,引用分为强引用、软引用、弱引用、虚引用4种,这4种引用的前度依次减弱。这样,当内存空间还足够时,有些对象可以作为缓存保留在内存中,进过垃圾收集后,如果仍然不够,则可以抛弃这些对象。
强引用:在程序代码中普遍存在的,类似Object.obj = new Object这类的引用,只要强引用还存在,垃圾收集器永远都不会回收掉被引用的对象。
软引用(SoftReference):是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
弱引用(WeekReference):也是用来描述非必须对象的。但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。WeakHashMap和ThreadLocal用到了弱引用。ThreadLocal线程本地变量,如果线程本身都已经消亡,那ThreadLocal中map保留的与对应线程相关的本地变量,自然也就没有用。
虚引用(PhantomReference):也称幽灵引用或幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过需引用来取得一个对象实例。jdk中直接内存的回收就用到虚引用。

以上几种引用的使用场景可以参考文章:
https://www.jianshu.com/p/825cca41d962

四、finalize()方法

java程序员,最好忘记有该方法的存在。finalize()方法只会被系统调用一次。

五、回收方法区

相关文章

网友评论

      本文标题:垃圾收集器与内存分配策略(一)——两种判断对象已死的方法

      本文链接:https://www.haomeiwen.com/subject/scqbkctx.html