`
Feiing
  • 浏览: 239267 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

理解 Java 的 GC 与 幽灵引用

阅读更多

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

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

	@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, 这也正是它名字的由来

	@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.

	@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. 对比

taken from http://mindprod.com/jgloss/phantom.html

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 的工作原理以及性能调优有一定帮助,   在实现一些基础性设施比如缓存时也可能会用到, 希望本文能有所帮助.

        王政 于 2009,6,3

       

 

分享到:
评论
29 楼 maxpana0177 2009-06-09  
还是有点不理解啊!
28 楼 jenlp520 2009-06-06  
kakaluyi 写道
为什么我们永远用不到的东东,可以被喷精。。。
,难道因为曲高,喷了就说明自己层次高


如果你是自己设计缓存的话 这其中还是很使用的
27 楼 kimmking 2009-06-06  
zhaomingzm_23 写道
幽灵引用?说真的不看看ref包下的类层次,还真不知道如何使用这些东西。
也许就是摩尔定义的原因吧,所以才出现了ConcurrentHashMap才有了G1

意思是:

使用PhantomReference 不用finalize

可以避免这个问题。
26 楼 zhaomingzm_23 2009-06-06  
幽灵引用?说真的不看看ref包下的类层次,还真不知道如何使用这些东西。
也许就是摩尔定义的原因吧,所以才出现了ConcurrentHashMap才有了G1
25 楼 kakaluyi 2009-06-05  
为什么我们永远用不到的东东,可以被喷精。。。
,难道因为曲高,喷了就说明自己层次高
24 楼 taowen 2009-06-05  
那就不是Concurrent HashMap啦,那是Synchronized的,效率差很远的。
23 楼 Feiing 2009-06-05  
taowen 写道
Cache必须基于ConcurrentHashMap吧。貌似你的getTable不能这么简单。。。


ConcurrentHashMap 的 key 并不是用 SoftReference 包装的, 所以要自己实现, 如果需要线程安全, 可以用 Collections.synchroziedMap(new SoftMap()) decorate 一下即可
22 楼 JavaScape 2009-06-05  
高人到处有 唯有我不是 哎 ~。~
21 楼 ansjsun 2009-06-05  
赐教了...这些东西在作web开发中会用到么???
20 楼 kaka11 2009-06-05  
freej 写道
使用 PhantomReference 就可以避免这个问题, 因为 PhantomReference 是在 finalize 方法执行后回收的,也就意味着此时已经不可能拿到原来的引用,  也就不会出现上述问题。

这句话说的有问题吧,不理解、

同问.
19 楼 jenlp520 2009-06-05  
taowen 写道
Cache必须基于ConcurrentHashMap吧。貌似你的getTable不能这么简单。。。


缓存基于线程安全上来说还是用ConcurrentHashMap 然后自己在把value用softreference包装下


对于你说那个如果key不在引用了 softreference就不会被清楚
我的想法是这样 如果softreference来说如果get为null那么显然对于这个缓存来说他是脏数据了
但是我想了想有2点
1:这个脏数据很小 如果数量很少的话几乎可以忽略不计 而且如果你在使用缓存的时候做了必要的判断 那么这个脏数据也完全不会破坏你的程序
2:这个脏数据应该很少,既然是用在缓存上 那么就应该是使用频率很大的 那么就象之前我说的

当然这2点也不能完全说明这个脏数据会影响程序的健壮 不过有利也有弊 还是自己权衡下在使用

ps:如果你愿意定时去遍历缓存然后清理也无妨....


18 楼 matt.u 2009-06-05  
恩,讲得很详细,有见地。

不过Java里面这么多种引用方式,对程序员来说还是比较复杂啊。

17 楼 taowen 2009-06-05  
Cache必须基于ConcurrentHashMap吧。貌似你的getTable不能这么简单。。。
16 楼 Feiing 2009-06-04  
taowen 写道
Feiing 写道
taowen 写道
如果我用一个ConcurrentHashMap<String, SoftReference>来持有我的Cache。那么我是应该是Finalizer呢还是ReferenceQueue来把SoftReference从cache中清除出去?目的就是把缓存尽可能长的时间持有,除非内存不够了。


应该用 ReferenceQueue 来实现, 可以参考 WeakHashMap 的实现, 另外缓存类型应该是  ConcurrentHashMap<SoftReference, Object>, 因为清除的依据肯定是 key 而非 value

Key如果是SoftReference,那我怎么取我的cache啊?


说错了, 应该是 key 应该被包装成 SoftReference, 代码大致会是这样 (事实上 WeakHashMap 就是这样实现的)

public class SoftHashMap<K, V> {
   
    /**
     * 用于清理无效 entries
     */
    private final ReferenceQueue<K> queue = new ReferenceQueue<K>();

    public V put(K key, V value) {

     ...
     new SoftEntry(key, value, queue);
     ...

    }

    public V get(K key) {
        int h = HashMap.hash(key.hashCode());
        Entry[] tab = getTable();
        int index = indexFor(h, tab.length);
        Entry<K,V> e = tab[index];
        while (e != null) {
            if (e.hash == h && eq(key, e.get()))
                return e.value;
            e = e.next;
        }
        return null;
    }
    
    /**
     * Returns the table after first expunging stale entries.
     */
    private Entry[] getTable() {
        expungeStaleEntries();
        return table;
    }
    
    /**
     *  清理无用 entries
     */
    private void expungeStaleEntries() {
	Entry<K,V> e;
        while ( (e = (Entry<K,V>) queue.poll()) != null) {
           remove e;
        }
    }


    private static final class SoftEntry<K, V> extends SoftReference<K> implements Map.Entry<K, V> {
      
      private V value;
      
      public SoftEntry(K key, V value, ReferenceQueue<K> queue) {
          super(key, queue);
          this.value = value;
      }

 
       public K getKey() {
           return get();
       }

       public V getValue() {
           return this.value;
       }

    }

}


15 楼 magnesium 2009-06-04  
不错不错,又学会了点东西
14 楼 taowen 2009-06-04  
jenlp520 写道
taowen 写道
jenlp520 写道
taowen 写道
如果我用一个ConcurrentHashMap<String, SoftReference>来持有我的Cache。那么我是应该是Finalizer呢还是ReferenceQueue来把SoftReference从cache中清除出去?目的就是把缓存尽可能长的时间持有,除非内存不够了。


应该是这样 如果你Sofrreference里面引用的对象没有别的引用的时候 在每次内存溢出前就会被自动清理掉

Object -> SoftReference -> Map
SoftReference对Object的引用会自动断开。但是SoftReference本身还会在Map中存在一个Entry。如何做到内存溢出的时候自动把SoftReference从Map中移除呢?


你用softReference来做缓存主要目的是为了长期持有对象而不引起内存泄露 那个对象自然就是被softReference引用的
既然在内存泄露前 你的大对象已经被清理了 你的这次危机自然就解除了 等你下一次用这个大对象的时候 你会发现softReference的get()是null了 那么你是不是会重新put一个新的进去呢
于是你上次的softReference会在下次gc调用的时候被清理掉了...

如果这个key再也不被引用了,就永远不会被清除了。这算不算是Memory Leak呢?
13 楼 taowen 2009-06-04  
Feiing 写道
taowen 写道
如果我用一个ConcurrentHashMap<String, SoftReference>来持有我的Cache。那么我是应该是Finalizer呢还是ReferenceQueue来把SoftReference从cache中清除出去?目的就是把缓存尽可能长的时间持有,除非内存不够了。


应该用 ReferenceQueue 来实现, 可以参考 WeakHashMap 的实现, 另外缓存类型应该是  ConcurrentHashMap<SoftReference, Object>, 因为清除的依据肯定是 key 而非 value

Key如果是SoftReference,那我怎么取我的cache啊?
12 楼 jenlp520 2009-06-04  
taowen 写道
jenlp520 写道
taowen 写道
如果我用一个ConcurrentHashMap<String, SoftReference>来持有我的Cache。那么我是应该是Finalizer呢还是ReferenceQueue来把SoftReference从cache中清除出去?目的就是把缓存尽可能长的时间持有,除非内存不够了。


应该是这样 如果你Sofrreference里面引用的对象没有别的引用的时候 在每次内存溢出前就会被自动清理掉

Object -> SoftReference -> Map
SoftReference对Object的引用会自动断开。但是SoftReference本身还会在Map中存在一个Entry。如何做到内存溢出的时候自动把SoftReference从Map中移除呢?


你用softReference来做缓存主要目的是为了长期持有对象而不引起内存泄露 那个对象自然就是被softReference引用的
既然在内存泄露前 你的大对象已经被清理了 你的这次危机自然就解除了 等你下一次用这个大对象的时候 你会发现softReference的get()是null了 那么你是不是会重新put一个新的进去呢
于是你上次的softReference会在下次gc调用的时候被清理掉了...
11 楼 Feiing 2009-06-04  
taowen 写道
如果我用一个ConcurrentHashMap<String, SoftReference>来持有我的Cache。那么我是应该是Finalizer呢还是ReferenceQueue来把SoftReference从cache中清除出去?目的就是把缓存尽可能长的时间持有,除非内存不够了。


应该用 ReferenceQueue 来实现, 可以参考 WeakHashMap 的实现, 另外缓存类型应该是  ConcurrentHashMap<SoftReference, Object>, 因为清除的依据肯定是 key 而非 value
10 楼 taowen 2009-06-04  
jenlp520 写道
taowen 写道
如果我用一个ConcurrentHashMap<String, SoftReference>来持有我的Cache。那么我是应该是Finalizer呢还是ReferenceQueue来把SoftReference从cache中清除出去?目的就是把缓存尽可能长的时间持有,除非内存不够了。


应该是这样 如果你Sofrreference里面引用的对象没有别的引用的时候 在每次内存溢出前就会被自动清理掉

Object -> SoftReference -> Map
SoftReference对Object的引用会自动断开。但是SoftReference本身还会在Map中存在一个Entry。如何做到内存溢出的时候自动把SoftReference从Map中移除呢?

相关推荐

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

    本文将深入探讨Java中的四种引用类型:强引用(StrongReference)、软引用(SoftReference)、弱引用(WeakReference)以及幽灵引用(PhantomReference),并讨论它们在GC工作原理和性能调优中的作用。 1. 强引用...

    JAVA GC 与 JVM调优1

    GC主要针对的是Java堆内存中的对象,这里的对象是由Java栈中的引用指向的。Java栈中每个线程都有一个独立的虚拟机栈,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。当对象不再有任何引用链(GC Roots)...

    java垃圾回收知识全集

    理解并掌握GC的工作原理对于优化Java应用程序的性能至关重要。以下是关于Java垃圾回收的一些关键知识点: 1. **对象的可回收判断**: - 引用计数算法:虽然Java不采用,但原理是跟踪每个对象的引用次数,当引用...

    全面解析Java中的引用类型

    本文将详细解析Java中的四种引用类型:强引用、软引用、弱引用和假象引用(幽灵引用)。 1. **强引用(Strong Reference)** - **定义**:强引用是最常见的引用类型,它代表了对象的常规引用。只要对象有强引用...

    Java基础复习笔记02对象状态、引用种类、垃圾回收形式[借鉴].pdf

    - **虚引用(Phantom Reference)**:也称为幽灵引用,无法直接获取对象,主要用于跟踪对象的回收,通常与引用队列配合使用。 3. **垃圾回收(Garbage Collection, GC)**:Java的自动内存管理机制,负责回收不再...

    JAVA基础面试笔试历年真题总结

    - **虚引用(Phantom Reference)**:也称为幽灵引用或幻影引用,无法通过虚引用获得一个对象实例。虚引用唯一的作用就是能在一个对象被收集器回收时收到一个系统通知。 #### 二、JVM的内存布局/内存模型 JVM的...

    搞定面试官:咱们从头到尾再说一次 Java 垃圾回收(csdn)————程序.pdf

    与C++等语言不同,Java的GC是自动进行的,程序员无需显式地调用释放内存的函数。 GC的执行并不能由程序员完全控制。虽然可以通过`System.gc()`尝试触发垃圾回收,但这并不保证垃圾回收一定会执行,因为是否执行以及...

    JAVA核心知识点整理.pdf

    4. **虚引用**:也称为幽灵引用或者幻影引用,无法通过虚引用获得对象,用来跟踪对象被垃圾回收的活动。 ### GC分代收集算法VS分区收集算法 1. **分代收集算法**:根据对象存活周期的不同将内存划分为几块,如...

    JAVA架构师知识整理.pdf

    综上所述,《JAVA架构师知识整理》这份文档涵盖了JVM的基本概念、内存管理机制、垃圾回收策略以及Java I/O编程等多个方面的重要知识点,对于深入理解Java语言特性和系统架构有着重要的意义。通过学习这些内容,架构...

    笔记,2、垃圾回收器和内存分配策略1

    4. 虚引用:也称幽灵引用,仅用于接收GC通知,对象何时被回收无法控制。 四、垃圾回收算法 1. 标记-清除算法:标记所有存活对象,然后清除其余对象,产生内存碎片问题。 2. 复制算法:将内存分为两部分,每次只用...

    java问题定位技术

    了解Java对象的生命周期、引用类型及垃圾回收机制对于有效定位和解决内存泄漏问题至关重要。 1. **Java对象的size**: - 对象的实际大小取决于其属性的类型和数量。 - 对象头部包含了元数据信息,如对象哈希码、...

    笔记,2、垃圾回收器和内存分配策略3

    4. 虚引用(幽灵引用):最弱的引用,仅用于在对象被回收时得到通知。 四、垃圾收集算法 1. 标记-清除算法:标记所有需要回收的对象,然后统一清除。缺点是可能导致大量内存碎片。 2. 复制算法:将内存分为两部分...

    java问题定位技术+性能优化

    ### Java问题定位技术+性能优化知识点详述 ...以上内容概述了Java问题定位技术和性能优化的主要知识点,通过深入理解和实践这些技术,可以帮助开发者有效地诊断和解决Java应用程序中的各种问题,提高应用程序的整体性能。

    JVM与性能优化知识点整理.pdf

    - **虚引用**:也称为幽灵引用或幻影引用,无法单独使用,必须和引用队列联合使用。虚引用主要用于跟踪对象被垃圾回收的状态。 ##### 3. 基本垃圾回收算法 垃圾回收算法是JVM自动管理内存的核心技术,主要包括以下...

Global site tag (gtag.js) - Google Analytics