JVM笔记(二)对象的生死与java的四大引用

垃圾回收,我们首先要判断一个对象是否是垃圾,即这个对象是否已经不再被使用到。

一、对象的生死

1.1 引用计数法

在对象中添加一个引用计数器,如果一个地方引用了它,则计数器+1,相应的一个引用失效时,计数器减一;计数器为0为不可再被使用。

原理简单,效率高,但无法解决循环引用问题。

主流的java虚拟机未使用此方法

public class _01Reference {
    public Object instance=null;

    private static final int _1MB=1024*1024;

    private byte[] bigSize=new byte[2*_1MB];
    /**
     * -XX:+PrintGC 输出GC日志
     * -XX:+PrintGCDetails 输出GC的详细日志
     * -XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
     * -XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
     * -XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
     * -Xloggc:../logs/gc.log 日志文件的输出路径
     * @param args
     */
    public static void main(String[] args) {
        _01Reference r1=new _01Reference();
        _01Reference r2=new _01Reference();

        r1.instance=r2;
        r2.instance=r1;

        r1=null;
        r2=null;

        System.gc();
    }
}

两个对象互相引用,但虚拟机没有因为两个对象互相引用而不进行GC

1.2 可达性分析法

该算法是当前主流的商用语法的内存管理子系统所采取的,也是JVM所采取的。

该算法试通过一系列**“GC Roots"的根对象作为起始节点集合**,从这些节开始根据引用关系向下搜索,搜索过程走过的路径称为“引用链“,如果某个对象到GC Roots没有引用链,说明此对象时不可被利用的。

简单说,就是从根节点集合的所有不可达的对象判定为死亡,可达的对象判定为活跃的。

在这里插入图片描述

如上:虽然对象5,6,7互相存在关联,但它们是从GC Roots不可达的,因为被判定为可回收对象。

可作为GC Roots的对象:

  1. 栈中(栈帧中的本地变量表)引用的对象

  2. 方法区中类静态属性引用的对象

  3. 方法区中常量引用的对象(如字符串常量池里的引用)

  4. 本地方法栈中JNI(Native方法)引用的对象

  5. 虚拟机内部引用(基本数据类型对应的Class对象,常驻的异常对象(NPE、OOM),系统类加载器)

  6. 所有被synchronize关键字持有的对象

1.3 引用

java引用分为 强引用、软引用、弱引用和虚引用

整体架构

位于java.lang.ref包下:

在这里插入图片描述

强引用

类似Object obj=new Object()这样的引用关系就叫做强引用

对于强引用的对象,即使出现OOM也不会回收该对象

Object obj1=new Object();
Object obj2=obj1;
obj1=null;
System.gc();
Sytem.out.printIn(obj2)

如上代码,obj2不会进行回收

软引用

位于java.lang.ref.SoftReference

一些有用,但非必须的对象。在系统将要出现内存溢出前,会将这些对象列为第二次回收对象,如果此次回收还没有足够内存,则抛出OOM。

即 内存够用 就 不回收

​ 内存不够用 就 回收 ----> 回收还不够 -----> OOM

用在内存敏感的地方,如高速缓存

 /**
     * 内存够用时 软引用对象不被回收
     */
    public static void memoryEnough(){
        Object obj1=new Object();
        SoftReference<Object>softReference=new SoftReference<>(obj1);
        System.out.println(obj1);
        System.out.println(softReference.get());

        obj1=null;
        System.gc();
        System.out.println(obj1);//null
        System.out.println(softReference.get());//有对象
    }

    /**
     * 内存不够用,会回收软引用对象
     * -Xms10M -Xmx10M -XX:+PrintGCDetails
     */
    public static void notEnough(){
        Object obj1=new Object();
        SoftReference<Object>softReference=new SoftReference<>(obj1);
        System.out.println(obj1);
        System.out.println(softReference.get());

        obj1=null;
        try {
            byte[]bytes=new byte[20*1024*1024];

        }finally {
            System.out.println(obj1);//null
            System.out.println(softReference.get());//null
        }

    }

软引用用途

缓存中会非常常用,我们使用软引用保存缓存数据,当发生OOM时,系统就会回收这些软引用的缓存数据。

弱引用

位于java.lang.ref.WeakReference

非必须对象,不管内存够不够用,都会被GC回收

 public static void main(String[] args) {
        Object o1=new Object();
        WeakReference<Object>weakReference=new WeakReference<>(o1);
        System.out.println(o1);//java.lang.Object@677327b6
        System.out.println(weakReference.get());//java.lang.Object@677327b6

        o1=null;
        System.gc();
        System.out.println(o1);//null
        System.out.println(weakReference.get());//null
    }

如上内存够用的话,发生GC仍然会被回收,内存不够也是会被回收的,这里就不演示了。

WeakHashMap

private static void weakHashMap() {
        WeakHashMap<Integer,Object> map=new WeakHashMap<>();
        Integer key=new Integer(1);
        Object value="value";
        map.put(key,value);
        System.out.println(map);//{1=value}
        key=null;
        System.gc();
        System.out.println(map);//{}
    }
private static void hashMap(){
        HashMap<Integer,String>map=new HashMap<>();
        Integer key=new Integer(1);
        String value="wml";
        map.put(key,value);
        System.out.println(map);//{1=wml}
        key=null;
        System.gc();
        System.out.println(map);//{1=wml}

    }

虚引用

位于java.lang.ref.PhantomReference

也称“幽灵引用”或“幻影引用”,最弱的引用关系

无法通过虚引用的get()来取得一个对象实例。

仅用于对象被回收时收到一个系统通知,或后续添加进一步处理,必须和引用队列一起使用。

 public static void main(String[] args) throws InterruptedException {
        Object o1=new Object();
        ReferenceQueue<Object>queue=new ReferenceQueue<>();
        PhantomReference<Object> phantomReference=new PhantomReference<>(o1, queue);

        System.out.println(o1);//java.lang.Object@677327b6
        System.out.println(phantomReference.get());//null
        System.out.println("引用队列:"+queue.poll());//null
        System.out.println("=============发生GC后========");

        o1=null;
        System.gc();
        System.out.println(o1);//null
        System.out.println(phantomReference.get());//null
        System.out.println("引用队列:"+queue.poll());//java.lang.ref.WeakReference@14ae5a5

    }

1.4 对象的自我拯救

一个对象的死亡至少需要两次标记。

第一次:即对GC Roots进行可达性分析后没有与之相连的引用连时

第二次:对象是否要执行finalize()方法

如果对象覆盖了finalize方法,并在该方法中重新与引用链上的任何一个对象建立连接,则代表该对象自我拯救成功,会被从被回收的集合中移除。

package com.wml.jvm.gc;

/**
 * 1.对象可以在被GB时进行逃逸
 * 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
 * @author MaoLin Wang
 * @date 2020/3/1216:39
 */
public class _02FinalizeEscapeGC {
    public static Object obj1 =null;

    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize方法执行");
        obj1 =this;
    }

    private  static void gc(){
        System.gc();
        System.out.println("开始垃圾回收");
    }
    public static void main(String[] args) throws InterruptedException {
        obj1 =new _02FinalizeEscapeGC();

        //对象第一次进行自我拯救(逃避GC)
        obj1 =null;
        gc();
        //因为finalize方法优先级很低,所有等待一下
        Thread.sleep(500);
        if (obj1 !=null){
            System.out.println("first->对象仍然存活");
        }else {
            System.out.println("first->对象已死亡");
        }

        //对象第二次进行自我拯救(逃避GC),但因为finalize已经被调用了一次,因此无法逃逸成功
        obj1 =null;
        System.gc();
        //因为finalize方法优先级很低,所有等待一下
        Thread.sleep(500);
        if (obj1 !=null){
            System.out.println("Second->对象仍然存活");
        }else {
            System.out.println("Second->对象已死亡");
        }

    }
}

结果:

调用了finalize方法
开始垃圾回收
first->对象仍然存活
Second->对象已死亡

但官方已不推荐这种做法,因为运行代价高,不确定性大。

1.5 方法区的回收

回收目标:

  1. 废弃的常量

    某个常量没有其他任何地方引用它时,就会被回收。常量池中其他类、方法、字段的符号引用

  2. 不再使用的类型

    类型被允许回收的条件如下:

    a.该类所有的实例都被回收

    b. 加载该类的类加载器已被回收

    c. 该类对应的java.class.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

    在大量使用反射、动态代理、CGLib等字节码框架都需要虚拟机能够进行类型卸载,以减少方法区压力。

======================================================================

其他相关笔记:

JVM笔记(一)java内存区域与内存溢出以及对象的创建、布局和定位

JVM笔记(三)垃圾收集算法以及HotSpot的算法实现(安全点、记忆集与卡表、写屏障、三色标记等)

JVM笔记《四》七个常见的垃圾收集器

JVM笔记(五)类加载机制、类加载器和双亲委派机制

================================================================

参考:
《深入理解java虚拟机第三版》

发布了75 篇原创文章 · 获赞 13 · 访问量 8369

猜你喜欢

转载自blog.csdn.net/weixin_43696529/article/details/104884444