全面解析Java中的GC与幽灵引用

 更新时间:2013年09月11日 10:05:22   作者:  
一般的应用程序不会涉及到 Reference 编程, 但是了解这些知识会对理解 GC 的工作原理以及性能调优有一定帮助,在实现一些基础性设施比如缓存时也可能会用到,希望本文能有所帮助

Java 中一共有 4 种类型的引用 : StrongReference、 SoftReference、 WeakReference 以及 PhantomReference (传说中的幽灵引用 呵呵),
这 4 种类型的引用与 GC 有着密切的关系,  让我们逐一来看它们的定义和使用场景 :

1、 Strong Reference
StrongReference 是 Java 的默认引用实现,  它会尽可能长时间的存活于 JVM 内, 当没有任何对象指向它时 GC 执行后将会被回收

Java代码

复制代码 代码如下:

@Test 
public void strongReference() { 
    Object referent = new Object(); 

    /**
     * 通过赋值创建 StrongReference 
     */ 
    Object strongReference = referent; 

    assertSame(referent, strongReference); 

    referent = null; 
    System.gc(); 

    /**
     * StrongReference 在 GC 后不会被回收
     */ 
    assertNotNull(strongReference); 


2、 WeakReference & WeakHashMap
WeakReference, 顾名思义,  是一个弱引用,  当所引用的对象在 JVM 内不再有强引用时, GC 后 weak reference 将会被自动回收
复制代码 代码如下:

@Test 
public void weakReference() { 
    Object referent = new Object(); 
    WeakReference<Object> weakRerference = new WeakReference<Object>(referent); 

    assertSame(referent, weakRerference.get()); 

    referent = null; 
    System.gc(); 

    /**
     * 一旦没有指向 referent 的强引用, weak reference 在 GC 后会被自动回收
     */ 
    assertNull(weakRerference.get()); 


WeakHashMap 使用 WeakReference 作为 key, 一旦没有指向 key 的强引用, WeakHashMap 在 GC 后将自动删除相关的 entry
复制代码 代码如下:

@Test 
public void weakHashMap() throws InterruptedException { 
    Map<Object, Object> weakHashMap = new WeakHashMap<Object, Object>(); 
    Object key = new Object(); 
    Object value = new Object(); 
    weakHashMap.put(key, value); 

    assertTrue(weakHashMap.containsValue(value)); 

    key = null; 
    System.gc(); 

    /**
     * 等待无效 entries 进入 ReferenceQueue 以便下一次调用 getTable 时被清理
     */ 
    Thread.sleep(1000); 

    /**
     * 一旦没有指向 key 的强引用, WeakHashMap 在 GC 后将自动删除相关的 entry
     */ 
    assertFalse(weakHashMap.containsValue(value)); 


3、SoftReference
SoftReference 于 WeakReference 的特性基本一致, 最大的区别在于 SoftReference 会尽可能长的保留引用直到 JVM 内存不足时才会被回收(虚拟机保证), 这一特性使得 SoftReference 非常适合缓存应用
复制代码 代码如下:

@Test 
public void softReference() { 
    Object referent = new Object(); 
    SoftReference<Object> softRerference = new SoftReference<Object>(referent); 

    assertNotNull(softRerference.get()); 

    referent = null; 
    System.gc(); 

    /**
     *  soft references 只有在 jvm OutOfMemory 之前才会被回收, 所以它非常适合缓存应用
     */ 
    assertNotNull(softRerference.get()); 


4、 PhantomReference
作为本文主角, Phantom Reference(幽灵引用) 与 WeakReference 和 SoftReference 有很大的不同,  因为它的 get() 方法永远返回 null, 这也正是它名字的由来

Java代码

复制代码 代码如下:

@Test 
public void phantomReferenceAlwaysNull() { 
    Object referent = new Object(); 
    PhantomReference<Object> phantomReference = new PhantomReference<Object>(referent, new ReferenceQueue<Object>()); 

    /**
     * phantom reference 的 get 方法永远返回 null 
     */ 
    assertNull(phantomReference.get()); 


诸位可能要问, 一个永远返回 null 的 reference 要来何用,  请注意构造 PhantomReference 时的第二个参数 ReferenceQueue(事实上 WeakReference & SoftReference 也可以有这个参数),
PhantomReference 唯一的用处就是跟踪 referent  何时被 enqueue 到 ReferenceQueue 中.

5、 RererenceQueue
当一个 WeakReference 开始返回 null 时, 它所指向的对象已经准备被回收, 这时可以做一些合适的清理工作.   将一个 ReferenceQueue 传给一个 Reference 的构造函数, 当对象被回收时, 虚拟机会自动将这个对象插入到 ReferenceQueue 中, WeakHashMap 就是利用 ReferenceQueue 来清除 key 已经没有强引用的 entries.

Java代码

复制代码 代码如下:

@Test 
public void referenceQueue() throws InterruptedException { 
    Object referent = new Object();      
    ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>(); 
    WeakReference<Object> weakReference = new WeakReference<Object>(referent, referenceQueue); 

    assertFalse(weakReference.isEnqueued()); 
    Reference<? extends Object> polled = referenceQueue.poll(); 
    assertNull(polled); 

    referent = null; 
    System.gc(); 

    assertTrue(weakReference.isEnqueued()); 
    Reference<? extends Object> removed = referenceQueue.remove(); 
    assertNotNull(removed); 


6、PhantomReference  vs WeakReference
PhantomReference  有两个好处, 其一, 它可以让我们准确地知道对象何时被从内存中删除, 这个特性可以被用于一些特殊的需求中(例如 Distributed GC,  XWork 和 google-guice 中也使用 PhantomReference 做了一些清理性工作).

其二, 它可以避免 finalization 带来的一些根本性问题, 上文提到 PhantomReference 的唯一作用就是跟踪 referent 何时被 enqueue 到 ReferenceQueue 中,  但是 WeakReference 也有对应的功能, 两者的区别到底在哪呢 ?

这就要说到 Object 的 finalize 方法, 此方法将在 gc 执行前被调用, 如果某个对象重载了 finalize 方法并故意在方法内创建本身的强引用,  这将导致这一轮的 GC 无法回收这个对象并有可能引起任意次 GC, 最后的结果就是明明 JVM 内有很多 Garbage 却 OutOfMemory, 使用 PhantomReference 就可以避免这个问题, 因为 PhantomReference 是在 finalize 方法执行后回收的,也就意味着此时已经不可能拿到原来的引用, 也就不会出现上述问题,  当然这是一个很极端的例子, 一般不会出现.

7、 对比

Soft vs Weak vs Phantom References
Type Purpose Use When GCed Implementing Class
Strong Reference An ordinary reference. Keeps objects alive as long as they are referenced. normal reference. Any object not pointed to can be reclaimed. default
Soft Reference Keeps objects alive provided there's enough memory. to keep objects alive even after clients have removed their references (memory-sensitive caches), in case clients start asking for them again by key. After a first gc pass, the JVM decides it still needs to reclaim more space. java.lang.ref.SoftReference
Weak Reference Keeps objects alive only while they're in use (reachable) by clients. Containers that automatically delete objects no longer in use. After gc determines the object is only weakly reachable java.lang.ref.WeakReference 
java.util.WeakHashMap
Phantom Reference Lets you clean up after finalization but before the space is reclaimed (replaces or augments the use offinalize()) Special clean up processing After finalization. java.lang.ref.PhantomReference

 

 

8. 小结
一般的应用程序不会涉及到 Reference 编程, 但是了解这些知识会对理解 GC 的工作原理以及性能调优有一定帮助,在实现一些基础性设施比如缓存时也可能会用到, 希望本文能有所帮助。

相关文章

  • JAVA 实现二叉树(链式存储结构)

    JAVA 实现二叉树(链式存储结构)

    本篇文章主要介绍用JAVA 实现二叉树,并提供实例.对二叉树数据结构很好的学习实践,有需要的朋友可以参考下
    2016-07-07
  • Hadoop环境配置之hive环境配置详解

    Hadoop环境配置之hive环境配置详解

    这篇文章主要介绍了Hadoop环境配置之hive环境配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12
  • SpringBoot整合RabbitMQ及生产全场景高级特性实战

    SpringBoot整合RabbitMQ及生产全场景高级特性实战

    本文主要介绍了SpringBoot整合RabbitMQ及生产全场景高级特性实战,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • Java之通过OutputStream写入文件与文件复制问题

    Java之通过OutputStream写入文件与文件复制问题

    这篇文章主要介绍了Java之通过OutputStream写入文件与文件复制问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • 使用SpringBoot进行身份验证和授权的示例详解

    使用SpringBoot进行身份验证和授权的示例详解

    在广阔的 Web 开发世界中,身份验证是每个数字领域的守护者,在本教程中,我们将了解如何以本机方式保护、验证和授权 Spring-Boot 应用程序的用户,并遵循框架的良好实践,希望对大家有所帮助
    2023-11-11
  • C#使用MySQLConnectorNet和MySQLDriverCS操作MySQL的方法

    C#使用MySQLConnectorNet和MySQLDriverCS操作MySQL的方法

    这篇文章主要介绍了C#使用MySQLConnectorNet和MySQLDriverCS操作MySQL的方法,相比普通方法能够在Windows下简化很多操作步骤,需要的朋友可以参考下
    2016-04-04
  • SpringBoot 注解事务声明式事务的方式

    SpringBoot 注解事务声明式事务的方式

    springboot使用上述注解的几种方式开启事物,可以达到和xml中声明的同样效果,但是却告别了xml,使你的代码远离配置文件。今天就扒一扒springboot中事务使用注解的玩法,感兴趣的朋友一起看看吧
    2017-09-09
  • Java集成和使用dl4j实现通过扫描图片识别快递单信息

    Java集成和使用dl4j实现通过扫描图片识别快递单信息

    这篇文章主要为大家详细介绍了Java如何使用DL4J搭建一个简单的图像识别模型,并将其集成到Spring Boot后端中从而实现通过扫描图片识别快递单信息,需要的可以参考下
    2024-12-12
  • 如何编写javascript的gulp插件

    如何编写javascript的gulp插件

    本文主要介绍了使用PMD进行代码审查的方法,具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • SpringBoot中的Thymeleaf模板

    SpringBoot中的Thymeleaf模板

    Thymeleaf 的出现是为了取代 JSP,虽然 JSP 存在了很长时间,并在 Java Web 开发中无处不在,但是它也存在一些缺陷。在这篇文中给大家介绍了这些缺陷所存在问题,对spring boot thymeleaf 模板相关知识感兴趣的朋友跟随小编一起看看吧
    2018-10-10

最新评论