Java 四大引用详解

一、 GC回收日志打印基本设置

启动设置:

-verbose:gc             //开启gc日志    
-XX:+PrintGCDetails     //打印gc详情
-XX:+PrintGCDateStamps  //打印gc时间戳
-XX:+PrintHeapAtGC      //在进行GC的前后打印出堆的信息
-Xloggc:gc.log          //日志输出名称
-Xms3M                  //初始内存
-Xmx4M                  //最大可用内存

日志各个字段含义简述:

2019-03-28  日期

10:02:40.628+0800: 执行回收时间 

0.080: 启动JVM到该次垃圾回收时间

GC/Full GC :垃圾回收类型

Allocation Failure : 垃圾回收的原因,此时为Eden区空间不足

ParNew/CMS/Metaspace : 垃圾回收区域 新生代/老年代/元空间

[PSYoungGen: 510K->504K(1024K)]  清理前占用 -> 清理后占用(新生代内存总大小)

PS代表新生代的是使用的是Parallel Scavenge收集器垃圾收集器

[ParOldGen]  Par代表老年代使用的是Parallel old垃圾回收器

0.0006474 secs 表示回收的时间

510K->536K(3584K)  整个堆内存清理前占用 ——> 清理后占用(对内存总大小)

[Metaspace: 3304K->3304K(1056768K)] 这部分表示的是方法区的内存大小变化

[Times: user=0.00 sys=0.00, real=0.00 secs]

user 代表的是用户状态耗时0.00,

sys 系统状态耗时0.00,

real CPU实际的执行时间为0.00

Ergonomics:HotSpot自动选择和调优引发的FullGC

二、了解其概念及其区别

我们在实际开发中,往往会执行一些方法加载一些参数值到内存中,后期随着业务的发展、方法的执行等等大量的数据存在内存中,结果导致内存占用过多而产生的溢出异常,因此我们期望自己的放到内存中的对象也具有生命周期(如:内存不足时,jvm会自动回收掉某些对象从而避免OOM的错误)这个时候就需要用到软引用和弱引用

从JDK 1.2之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱。

强引用:

Java中默认声明的就是强引用,如:Object object=new Object();只要强引用还存在,JVM必定不会回收掉被引用的对象,即使在内存不足的情况下,JVM宁愿抛出OOM异常,也不会回收这种对象;
如下代码:

public class StronglyReference {
    public static void main(String[] args) {
        Object object=new Object();
        System.gc();
        System.out.println("GC回收后:"+object);
        try {
            byte[] buff2 = new byte[1024 * 1024 * 4];
        } catch (OutOfMemoryError e) {
            //为了程序继续执行下去,捕获异常,如果不捕获会抛出异常且回收
            System.out.println("创建对象发生OOM异常,哥,你宁愿发生异常也不回收掉哎,何必呢!");
        }
        //将引用设置为null,jvm在适当的时候会回收该对象
        object=null;
        System.out.println("设置为空后:"+object);
    }
}

结果产出:

GC回收后:java.lang.Object@12a3a380
创建对象发生OOM异常,哥,你宁愿发生异常也不回收掉哎,何必呢!
设置为空后:null

经过上面的demo实例,然后分析GC日志,我们得出下面的问题和答案;
问:那么在使用强引用的时候,JVM是么时候会触发回收?
答:

  1. 线程结束时回收;
  2. 当 softReference 设置为空的时候,会被回收;

软引用(SoftReference):

官方解释:软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。
查看一下事例:

public class SoftReferences {
    public static void main(String[] args) {
        List list=new ArrayList();
        for (int i=0;i<10;i++){
            byte[] softBuff = new byte[1024 * 1024 * 1];
            SoftReference sr = new SoftReference(softBuff);
            list.add(sr);
        }
        for (SoftReference sr:list) {
            System.out.println(sr.get());
        }
    }
}

返回结果:

null
null
null
null
null
null
null
null
null
[B@12a3a380

在GC日志中我们可以看到一个关键字出现:Ergonomics(解释上面有说明)
由此我们可以得出一个结论与解释一致
(还有一种情况,当软引用设置为空时也会被回收)
事例代码:

public static void main(String[] args) {
    byte[] softBuff1 = new byte[1024 * 1024 * 2];
    SoftReference sr1 = new SoftReference(softBuff1);
    sr1=null;
    System.out.println(sr1.get());
}

result:

Exception in thread "main" java.lang.NullPointerException

由此可见,设置为空时,对象也被回收了;
弱引用(WeakReference):
这个其实挺简单的:只要JVM进行垃圾回收,无论内存是否充足,都会回收弱引用关联的对象,在 java 中,用 java.lang.ref.WeakReference类来表示
事例:

public class WeakReferences {
    public static void main(String[] args) {
        try {
            WeakReference w1=new WeakReference(new Object());
            System.out.println(w1.get());
            System.gc();
            System.out.println(w1.get());
        } catch (NullPointerException e) {
            System.out.println("W1 会抛出空指针异常,表示被回收");
        }
    }
}

result:

java.lang.Object@12a3a380
null

虚引用(PhantomReference):

官方解释:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用PhantomReference 类来表示;我们看下源码
源码:

public class PhantomReference extends Reference {
    
    public T get() {
        return null;
    }
    
    public PhantomReference(T referent, ReferenceQueue q) {
        super(referent, q);
    }
}

通过源码我们可以清晰的了解到,它只有一个构造函数和一个 get()方法,而且 get()方法是固定的返回的null,这样玩们就知道了,我们永远无法单独的通过虚引用来获取对象,虚引用也必须要和ReferenceQueue引用队列一起使用。这是是它和软引用、弱引用最直观的局别;

引用队列(ReferenceQueue)

官方解释:引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。

与软引用、弱引用不同,虚引用必须和引用队列一起使用(上面有提到过)。

public class PhantomReferences {
    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue queue=new ReferenceQueue();
        Object phantomObject=new Object();
        PhantomReference phantomReference=new PhantomReference (phantomObject,queue);
        System.out.println(phantomReference.get());
        phantomObject=null;
        System.gc();
        System.out.println(phantomReference.get());
        Thread.sleep(200);
        System.out.println(queue.poll());
    }
}

result:

null
null
java.lang.ref.PhantomReference@12a3a380

问:为什么会有对象输出呢?
答:我们已经创建引用队列,并且在引用队列里面创建了对象phantomObject,当GC回收的时候发现虚引用,会将这个对象丢到队列里面,而现在插入队列的时候发现已经有了这个对象,会失败,然后会回收掉这个对象并且返回,所以会有返回对象

你可能感兴趣的:(Java)