java对象的四种引用分析

1.强引用

强引用是我们日常使用最多的引用方式,具体表现形式为创建一个对象并把这个对象直接赋给一个变量,强引用的对象遵循正常的JAVA垃圾回收规则,如果强引用对象大小超过堆内存大小,系统将会抛出异常。看下面的例子

public class Test {
    static final int MB = 1024 * 1024;

    byte[] b = new byte[4 * MB];

    public static void main(String[] args) {
        Test test1 = new Test();
        System.gc();
        System.out.println(test1);
        Test test2 = new Test();
    }
}

我们以一下配置运行代码

-Xms7M -Xmx7M -XX:+PrintGCDetails

限制堆内存最大7Mb,并且打印GC细节日志
运行的结果有如下几个部分

[GC (System.gc()) [PSYoungGen: 726K->432K(2048K)] 4822K->4536K(7680K), 0.0024028 secs] [Times: user=0.01 sys=0.01, real=0.01 secs] 
[Full GC (System.gc()) [PSYoungGen: 432K->0K(2048K)] [ParOldGen: 4104K->4433K(5632K)] 4536K->4433K(7680K), [Metaspace: 2649K->2649K(1056768K)], 0.0046611 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

首先是我们手动调用System.gc()触发的GC和Full GC,可以看到我们创建的Test对象没有被回收,4Mb大小的byte数组直接存放在ParOldGen中。
接下来我们再创建一个Test对象。

[GC (Allocation Failure) [PSYoungGen: 30K->64K(2048K)] 4464K->4497K(7680K), 0.0004474 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 64K->64K(2048K)] 4497K->4497K(7680K), 0.0003538 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 64K->0K(2048K)] [ParOldGen: 4433K->4422K(5632K)] 4497K->4422K(7680K), [Metaspace: 2651K->2651K(1056768K)], 0.0039247 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4422K->4422K(7680K), 0.0003716 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4422K->4410K(5632K)] 4422K->4410K(7680K), [Metaspace: 2651K->2651K(1056768K)], 0.0036486 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 

可以看到由于可用内存不足,JVM进行了3次GC和2次Full GC,结果都是Allocation Failure,这下JVM没有办法只能打出GG抛出异常。

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at test.Test.<init>(Test.java:9)
	at test.Test.main(Test.java:15)

2.软引用

软引用需要通过SoftReference类实现,软引用的对象也遵循正常的JAVA垃圾回收规则,但是在内存非常紧张的时候无论对象是否还被引用,都会被回收,所以在使用之前要判断是否为null从而判断他是否已经被回收了。下面是例子

public class Test {
    static final int MB = 1024 * 1024;

    byte[] b = new byte[2 * MB];

    public static void main(String[] args) {
        SoftReference<Test> test1 = new SoftReference<>(new Test());
        SoftReference<Test> test2 = new SoftReference<>(new Test());
        System.gc();
        System.out.println(test1.get());
        System.out.println(test2.get());
        Test test3 = new Test();
        System.out.println(test1.get());
        System.out.println(test2.get());
    }
}

用同样的参数执行代码,执行结果如下

[GC (System.gc()) [PSYoungGen: 726K->432K(2048K)] 4822K->4536K(7680K), 0.0018798 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 432K->0K(2048K)] [ParOldGen: 4104K->4433K(5632K)] 4536K->4433K(7680K), [Metaspace: 2650K->2650K(1056768K)], 0.0109155 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
test.Test@2503dbd3
test.Test@4b67cf4d
[GC (Allocation Failure) [PSYoungGen: 30K->64K(2048K)] 4464K->4497K(7680K), 0.0004542 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 64K->96K(2048K)] 4497K->4529K(7680K), 0.0003047 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 96K->0K(2048K)] [ParOldGen: 4433K->4423K(5632K)] 4529K->4423K(7680K), [Metaspace: 2651K->2651K(1056768K)], 0.0046343 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4423K->4423K(7680K), 0.0002881 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4423K->315K(5632K)] 4423K->315K(7680K), [Metaspace: 2651K->2651K(1056768K)], 0.0037538 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
null
null

同样是手动调用System.gc()触发的GC和Full GC,可以看到两个Test对象都没有被回收,两个2Mb大小的byte数组直接存放在ParOldGen中。
接下来我们再创建一个Test对象。这时候ParOldGen区的内存已经不足了,这时候jvm又祭出GC大法,在最后一次Full GC时发现有些对象是软引用的,全部干掉。然后顺利构造出一个新的Test对象。
需要注意的是,在强制清除软引用对象时,jvm并不会量入为出,需要2M空间就清理出2M空间,而是一次性将所有软引用对象都清除掉。
软引用最主要的应用就是WeakHashMap,WeakHashMap的key都是软引用的,因此不需要显示的去remove掉这个key,让map对Object的强引用断开,在内存不足时可以自动被回收。

3.弱引用

软引用是通过WeakReference类实现的,弱引用的对象只要碰上GC,就会被回收。下面是例子

public class Test {
    static final int MB = 1024 * 1024;

    byte[] b = new byte[2 * MB];

    public static void main(String[] args) {
        WeakReference<Test> test1 = new WeakReference<>(new Test());
        System.gc();
        System.out.println(test1.get());
    }
}

执行结果如下

[GC (System.gc()) [PSYoungGen: 726K->432K(2048K)] 2774K->2488K(7680K), 0.0010834 secs] [Times: user=0.01 sys=0.01, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 432K->0K(2048K)] [ParOldGen: 2056K->337K(5632K)] 2488K->337K(7680K), [Metaspace: 2649K->2649K(1056768K)], 0.0045870 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
null

在调用System.gc()之后,ParOldGen区中的内存被释放,虽然test1的引用仍在,但是依然被回收了。

4.虚引用

虚引用通过PhantomReference类来实现,主要用于追踪对象被垃圾回收的状态,需要配合ReferenceQueue来使用。示例如下

public class Test {
    static final int MB = 1024 * 1024;

    byte[] b = new byte[2 * MB];

    public static void main(String[] args) {
        ReferenceQueue<Test> queue = new ReferenceQueue<>();
        PhantomReference<Test> test1 = new PhantomReference<>(new Test(),queue);
        System.out.println(queue.poll());
        System.out.println(test1.isEnqueued());
        System.gc();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(queue.poll());
        System.out.println(test1.isEnqueued());
    }
}

首先创建一个虚引用并绑定在ReferenceQueue中,我们可以调用Reference对象的isEnqueued方法来查看引用对象是否已经被回收进入队列,可以看到此时返回false,ReferenceQueue也是一个空队列。
然后我们调用System.gc()并稍加等待,此时可以看到test1这个Reference对象已经被存入了队列,引用的Test对象也已经被回收。

5.ReferenceQueue

不仅是虚引用,其余的两种引用也可以和ReferenceQueue搭配使用。
示例代码如下

public class Test {
    static final int MB = 1024 * 1024;

    byte[] b = new byte[2 * MB];

    public static void main(String[] args) {
        ReferenceQueue<Test> queue = new ReferenceQueue<>();
        WeakReference<Test> test1 = new WeakReference<>(new Test(),queue);
        SoftReference<Test> test2 = new SoftReference<>(new Test(),queue);
        System.out.println(test1.isEnqueued());
        System.out.println(test2.isEnqueued());
        System.gc();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(test1.isEnqueued());
        System.out.println(test2.isEnqueued());
        Test test3 = new Test();
        Test test4 = new Test();
        System.out.println(test1.isEnqueued());
        System.out.println(test2.isEnqueued());
    }
}

首先我们构造了一个软引用和一个弱引用并绑定在ReferenceQueue上,此时两个引用调用isEnqueued方法返回都是false。然后调用System.gc(),test1引用的对象被回收,test1被压入ReferenceQueue队列。接着创建两个Test对象,此时内存紧张,test2引用的对象被回收,test2被压入ReferenceQueue队列。如果在构建引用时不使用ReferenceQueue对象作为参数,Reference对象调用isEnqueued方法返回永远都是false,要判断其引用对象是否已经被回收,就需要调用get()方法然后判空了。
因此使用ReferenceQueue在跟踪对象回收状态时更方便,通过ReferenceQueue可以及时知晓哪些引用已被回收,可以及时回收无效的Reference对象,节省空间,而不用定时遍历所有Reference对象来判断。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值