JVM学习(8)-Java中的四种引用总结

1.0、四种引用

JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。

JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为Strong强引用、Soft软引用、Weak弱引用、Phantom虚引用四种(引用强度逐渐减弱),它们被用来标识对象在GC过程中的生命周期,使得虚拟机更合理地根据内存的大小和对象作用选择是否回收掉这些引用。

以下为本文所要介绍的关键类图:
在这里插入图片描述

2.0、强引用-StrongReference

当内存空间不足时,Java虚拟机宁愿抛出OOM异常也不会对强引用对象进行回收。这是绝大部分的Java对象所处的引用。采用new或者反射创建的对象,都属于强引用,我们可以使用下面的程序进行验证:

public static void main(String[] args) {
        testStrongReference();
    }
    /*
     * @Author ARong
     * @Description 测试强引用在内存不足时是否回收(内存不够用也不回收,直接抛OOM) 	
     * -Xmx1024k -XX:+PrintGCDetails
     * @Param []
     * @return void
     **/
    private static void testStrongReference() {
        Object o1 = new Object();
        Object o2 = o1;
        o1 = null;
        System.out.println("=====GC前=====");
        System.out.println(o1);
        System.out.println(o2);
        // 强制分配1025kb
        try {
            byte[] allocated = new byte[1024 * 1025];
        } catch (Throwable t) {
            t.printStackTrace();
        }finally {
            System.out.println("=====GC后=====");
            System.out.println(o1);
            System.out.println(o2);
        }
    }

运行程序,可以看到属于强引用的对象,在发生GC时也不会被回收掉:

[GC (Allocation Failure) [PSYoungGen: 512K->320K(1024K)] 512K->320K(1536K), 0.0008693 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 825K->496K(1024K)] 825K->512K(1536K), 0.0015158 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 994K->512K(1024K)] 1010K->600K(1536K), 0.0009526 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
=====GC前=====
null
java.lang.Object@5acf9800
[GC (Allocation Failure) [PSYoungGen: 729K->512K(1024K)] 817K->624K(1536K), 0.0020629 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 512K->496K(1024K)] 624K->624K(1536K), 0.0009776 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 496K->481K(1024K)] [ParOldGen: 128K->59K(512K)] 624K->540K(1536K), [Metaspace: 3045K->3045K(1056768K)], 0.0049371 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 481K->496K(1024K)] 540K->563K(1536K), 0.0008344 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 496K->468K(1024K)] [ParOldGen: 67K->56K(512K)] 563K->524K(1536K), [Metaspace: 3045K->3045K(1056768K)], 0.0046858 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
java.lang.OutOfMemoryError: Java heap space
	at jvm_test.ReferenceTest.testStrongReference(ReferenceTest.java:36)
	at jvm_test.ReferenceTest.main(ReferenceTest.java:15)
=====GC后=====
null
java.lang.Object@5acf9800
Heap
 PSYoungGen      total 1024K, used 577K [0x00000007bfe80000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 512K, 21% used [0x00000007bfe80000,0x00000007bfe9b4c0,0x00000007bff00000)
  from space 512K, 91% used [0x00000007bff80000,0x00000007bfff51d0,0x00000007c0000000)
  to   space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
 ParOldGen       total 512K, used 56K [0x00000007bfe00000, 0x00000007bfe80000, 0x00000007bfe80000)
  object space 512K, 11% used [0x00000007bfe00000,0x00000007bfe0e190,0x00000007bfe80000)
 Metaspace       used 3127K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 342K, capacity 388K, committed 512K, reserved 1048576K

3.0、软引用-SoftReference

当内存空间充足时,不对其回收;当内存空间不足时,对其进行回收。一般用于实现缓存,当内存不足时对缓存的对象进行回收,以免OOM。

/*
     * @Author ARong
     * @Description 测试弱引用在内存不足时是否回收(内存不够则回收,避免OOM) -
     * Xmx1024k -XX:+PrintGCDetails
     * @Param []
     * @return void
     **/
    private static void testSoftReference() {
        Object strongObject = new Object();
        SoftReference<Object> softReference = new SoftReference<Object>(strongObject);
        strongObject = null;
        // GC前
        System.out.println("=====GC前=====");
        System.out.println(strongObject);
        System.out.println(softReference.get());
        // 强制分配1025kb
        try {
            byte[] allocated = new byte[1024 * 1025];
        } catch (Throwable t) {
            t.printStackTrace();
        }finally {
            System.out.println("=====GC后=====");
            System.out.println(strongObject);
            System.out.println(softReference.get());
        }
    }

运行程序,可以发现在内存空间空间充足时,不会对软引用进行回收,而分配1025kb内存后,内存不足,马上对软引用进行了回收。所以软引用适合于作为本地缓存对象来使用,当内存够用,那么可以使用多余的对象作为缓存,当内存不够用,将其进行回收处理。

[GC (Allocation Failure) [PSYoungGen: 512K->368K(1024K)] 512K->368K(1536K), 0.0120415 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 873K->496K(1024K)] 873K->512K(1536K), 0.0011171 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 994K->512K(1024K)] 1010K->600K(1536K), 0.0007085 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
=====GC前=====
null
java.lang.Object@5acf9800
[GC (Allocation Failure) [PSYoungGen: 773K->512K(1024K)] 861K->704K(1536K), 0.0011383 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 512K->512K(1024K)] 704K->704K(1536K), 0.0006836 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 512K->444K(1024K)] [ParOldGen: 192K->123K(512K)] 704K->567K(1536K), [Metaspace: 3071K->3071K(1056768K)], 0.0043012 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 444K->480K(1024K)] 567K->603K(1536K), 0.0008256 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 480K->436K(1024K)] [ParOldGen: 123K->115K(512K)] 603K->551K(1536K), [Metaspace: 3071K->3071K(1056768K)], 0.0052903 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
java.lang.OutOfMemoryError: Java heap space
	at jvm_test.ReferenceTest.testSoftReference(ReferenceTest.java:63)
	at jvm_test.ReferenceTest.main(ReferenceTest.java:16)
=====GC后=====
null
null
Heap
 PSYoungGen      total 1024K, used 543K [0x00000007bfe80000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 512K, 20% used [0x00000007bfe80000,0x00000007bfe9adb0,0x00000007bff00000)
  from space 512K, 85% used [0x00000007bff80000,0x00000007bffed208,0x00000007c0000000)
  to   space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
 ParOldGen       total 512K, used 115K [0x00000007bfe00000, 0x00000007bfe80000, 0x00000007bfe80000)
  object space 512K, 22% used [0x00000007bfe00000,0x00000007bfe1cc98,0x00000007bfe80000)
 Metaspace       used 3199K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 348K, capacity 388K, committed 512K, reserved 1048576K

4.0、弱引用-WeakReference

垃圾回收器一旦扫描到软引用对象就会立即将其回收

/*
     * @Author ARong
     * @Description 测试弱引用在内存不足时是否回收(只要扫描到,内存够和不够都回收,避免OOM)
     * @Param []
     * @return void
     **/
    private static void testWeakReference() {
        Object strongObject = new Object();
        WeakReference<Object> weakReference = new WeakReference<Object>(strongObject);
        strongObject = null;
        System.out.println("=====GC扫描前=====");
        System.out.println(strongObject);
        System.out.println(weakReference.get());
        // GC扫描
        System.gc();
        System.out.println("=====GC扫描后=====");
        System.out.println(strongObject);
        System.out.println(weakReference.get());
    }

程序之后后可发现弱引用只要被GC扫描到,内存够和不够都回收。

[GC (Allocation Failure) [PSYoungGen: 512K->384K(1024K)] 512K->384K(1536K), 0.0020183 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 889K->496K(1024K)] 889K->520K(1536K), 0.0027958 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 994K->512K(1024K)] 1018K->608K(1536K), 0.0011595 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
=====GC扫描前=====
null
java.lang.Object@5acf9800
[GC (System.gc()) [PSYoungGen: 765K->496K(1024K)] 861K->656K(1536K), 0.0018561 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 496K->478K(1024K)] [ParOldGen: 160K->86K(512K)] 656K->564K(1536K), [Metaspace: 3069K->3069K(1056768K)], 0.0051843 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
=====GC扫描后=====
null
null
Heap
 PSYoungGen      total 1024K, used 569K [0x00000007bfe80000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 512K, 17% used [0x00000007bfe80000,0x00000007bfe96d48,0x00000007bff00000)
  from space 512K, 93% used [0x00000007bff80000,0x00000007bfff7888,0x00000007c0000000)
  to   space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
 ParOldGen       total 512K, used 86K [0x00000007bfe00000, 0x00000007bfe80000, 0x00000007bfe80000)
  object space 512K, 16% used [0x00000007bfe00000,0x00000007bfe15948,0x00000007bfe80000)
 Metaspace       used 3177K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 345K, capacity 388K, committed 512K, reserved 1048576K

5.0、虚引用-PhnatomReference

虚引用并不会决定对象的生命周期,虚引用主要用来跟踪对象被垃圾回收的活动。虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了改虚引用,来了解虚引用标记的对象是否已经被回收,引用队列的作用是用来作为通知

/*
     * @Author ARong
     * @Description 测试虚引用在内存不足时是否回收  
     * -Xmx1024k -XX:+PrintGCDetails
     * @Param []
     * @return void
     **/
    private static void testPhnatomReference() {
        //虚引用并不起任何作用,而是和引用队列一起使用,当虚引用标记的对象被回收时,引用队列中将出现该虚引用
        Object strongObject = new Object();
        // 引用队列
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        // 创建虚引用,并且与引用队列建立关联
        PhantomReference<Object> phantomReference = new PhantomReference<>(strongObject, referenceQueue);
        System.out.println("=====GC前=====");
        System.out.println(strongObject);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());

        strongObject = null;
        // 强制分配1025kb, GC
        try {
            byte[] allocated = new byte[1024 * 1025];
        } catch (Throwable t) {
            t.printStackTrace();
        }finally {
            System.out.println("=====GC后=====");
            System.out.println(strongObject);
            System.out.println(phantomReference.get());
            System.out.println(referenceQueue.poll());
        }
    }

执行程序发现,在GC发生前,虚引用中并不存在对象的引用,并且引用队列也是空的。当GC发生后,虚引用标识的对象被回收,引用队列中也出现了相应的引用,这也是虚引用存在的主要意义:作为对象被回收后的通知。

GC (Allocation Failure) [PSYoungGen: 512K->384K(1024K)] 512K->384K(1536K), 0.0009170 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 889K->480K(1024K)] 889K->504K(1536K), 0.0010306 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 978K->480K(1024K)] 1002K->608K(1536K), 0.0009136 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
=====GC前=====
java.lang.Object@5acf9800
null
null
[GC (Allocation Failure) [PSYoungGen: 699K->512K(1024K)] 827K->656K(1536K), 0.0007388 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 512K->496K(1024K)] 656K->656K(1536K), 0.0004858 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 496K->456K(1024K)] [ParOldGen: 160K->84K(512K)] 656K->541K(1536K), [Metaspace: 3050K->3050K(1056768K)], 0.0041626 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 456K->496K(1024K)] 541K->596K(1536K), 0.0005543 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 496K->444K(1024K)] [ParOldGen: 100K->81K(512K)] 596K->525K(1536K), [Metaspace: 3050K->3050K(1056768K)], 0.0046933 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
java.lang.OutOfMemoryError: Java heap space
	at jvm_test.ReferenceTest.testPhnatomReference(ReferenceTest.java:116)
	at jvm_test.ReferenceTest.main(ReferenceTest.java:18)
=====GC后=====
null
null
java.lang.ref.PhantomReference@36baf30c
Heap
 PSYoungGen      total 1024K, used 516K [0x00000007bfe80000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 512K, 14% used [0x00000007bfe80000,0x00000007bfe92178,0x00000007bff00000)
  from space 512K, 86% used [0x00000007bff80000,0x00000007bffef030,0x00000007c0000000)
  to   space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
 ParOldGen       total 512K, used 81K [0x00000007bfe00000, 0x00000007bfe80000, 0x00000007bfe80000)
  object space 512K, 15% used [0x00000007bfe00000,0x00000007bfe14560,0x00000007bfe80000)
 Metaspace       used 3105K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 342K, capacity 388K, committed 512K, reserved 1048576K

6.0、小结

四种引用类型在GC中回收的情况就介绍完了,再进行以下总结:

  1. 强引用(大部分对象,new出来的对象)
    当内存空间不足时,Java虚拟机宁愿抛出OOM异常也不会对强引用对象进行回收。这是绝大部分的Java对象所处的引用。

  2. 软引用(SoftReference)
    当内存空间充足时,不对其回收;当内存空间不足时,对其进行回收。一般用于实现缓存,当内存不足时对缓存的对象进行回收,以免OOM。

  3. 弱引用 (WeakReference)
    垃圾回收器一旦扫描到软引用对象就会立即将其回收

  4. 虚引用 (PhantomReference)
    虚引用并不会决定对象的生命周期,虚引用主要用来跟踪对象被垃圾回收的活动。虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了改虚引用,来了解虚引用标记的对象是否已经被回收,引用队列的作用是用来作为通知

7.0、题外话-WeakHashMap

了解完了四种引用的区别,接下来我们了解一下WeakHashMap。WeakHashMap一般用于缓存数据的存储,当key失效时,value也会被GC清除,以达到节约内存使用的目的。

两者的对比

/**
 * @Auther: ARong
 * @Description: 测试WeakHashMap:用作缓存
 */
public class WeakHashMapTest {
    public static void main(String[] args) {
        hashMapTest();
        System.out.println("==========");
        weakHashMapTest();
    }

    /*
     * @Author ARong
     * @Description 测试HashMap在key为null时发生GC其对应的值是否会回收(不回收)
     * @Param []
     * @return void
     **/
    private static void hashMapTest() {
        HashMap<Integer, String> hashMap = new HashMap<>();
        Integer key = new Integer(1);
        String value = "hashMap";
        hashMap.put(key, value);
        key = null;
        System.gc();
        System.out.println(hashMap);
    }


    /*
     * @Author ARong
     * @Description 测试WeakHashMap在key为null时发生GC其对应的值是否会回收(回收)
     * @Param []
     * @return void
     **/
    private static void weakHashMapTest() {
        WeakHashMap<Integer, String> weakHashMap = new WeakHashMap<>();
        Integer key = new Integer(1);
        String value = "hashMap";
        weakHashMap.put(key, value);
        key = null;
        System.gc();
        System.out.println(weakHashMap);
    }
}

程序执行之后的结果

objc[29697]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/bin/java (0x10f0a54c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x10f11b4e0). One of the two will be used. Which one is undefined.
{1=hashMap}
==========
{}

可以看到,在key置为null之后,HashMap中的value还是存在的;而WeakHashMap的key在清空之后,发生GC时已经把相关联的value清除了,减少了内存的占用空间。

发布了309 篇原创文章 · 获赞 205 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/pbrlovejava/article/details/104095852
今日推荐