论坛首页 Java企业应用论坛

理解 Java 的 GC 与 幽灵引用

浏览 60001 次
该帖已经被评为精华帖
作者 正文
   发表时间:2009-06-11  
taowen 写道
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呢?


不被清除的对象未必都是Memory Leak。
一般来说,缓存或者静态数据应该是贯穿整个application的生命周期(假设没有任何的缓存策略),此时key是被有意识的引用或者说是保存:Map(global or static)-Entry-Key。所以对于缓存中所有的数据,可不能说,用不到的就是Memory Leak。正如湖人打魔术,奥兰多混好了,就不回洛杉矶了,可洛杉矶的场馆咱照样得维护好了~~
0 请登录后投票
   发表时间:2009-06-15  
taowen 写道
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呢?


针对以上各位说的问题,对于Map当中的key,可以做一个简单的修改,代码如下:
先有一个工具类,专用于获得key值的。
public class ToolUtil {
    //作为缓存的Map
    public static final ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();
    public static ExpandSoftReference getKey(String key){
        return new ExpandSoftReference(new ExpandSring(key));
    }
    private static class ExpandSring{
        private String key;
        private ExpandSring() {
        }

        private ExpandSring(String key) {
            this.key = key;
        }

        public String getKey() {
            return key;
        }

        public void setKey(String key) {
            this.key = key;
        }

        protected void finalize() throws Throwable {
            super.finalize();
            concurrentHashMap.remove(new ExpandSoftReference(this));
        }

        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ExpandSring that = (ExpandSring) o;

            if (key != null ? !key.equals(that.key) : that.key != null) return false;

            return true;
        }

        public int hashCode() {
            return (key != null ? key.hashCode() : 0);
        }
    }
}
ExpandSoftReference类内容如下:
public class ExpandSoftReference extends SoftReference {
    private String relativelyStr;
    public ExpandSoftReference(Object referent) {
        super(referent);
        relativelyStr = referent.toString()+"_key";
    }

    public ExpandSoftReference(Object referent, ReferenceQueue q) {
        super(referent, q);
        relativelyStr = referent.toString()+"_key";
    }

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ExpandSoftReference that = (ExpandSoftReference) o;

        if (relativelyStr != null ? !relativelyStr.equals(that.relativelyStr) : that.relativelyStr != null)
            return false;

        return true;
    }

    public int hashCode() {
        return (relativelyStr != null ? relativelyStr.hashCode() : 0);
    }
}
我在使用时如下这样,便可以了
ToolUtil.concurrentHashMap.put(ToolUtil.getKey("snsky"),new Object());
此扩展有以下几个关键点所在
ExpandSring类扩展了
finalize方法
在ExpandSring类对象占用的内存被回收之前释放在Map当中对应key-value对
ExpandSoftReference类扩展了"软引用类"(SoftReference)
并且为了可以跟据key值查找到对应的value
此类当中用relativelyStr属性作为比较的值。在获取方法时可以用
ToolUtil.concurrentHashMap.get(ToolUtil.getKey("snsky"));
以上代表我个人的一些想法,希望能集思广义,看是否有不合理的地方。


0 请登录后投票
   发表时间:2009-06-16  
ReferenceQueue 的作用是不是只要是在它里面的对象
当垃圾收集时一定会被当垃圾清除,不管这个对象是否还被其他引用???

0 请登录后投票
   发表时间:2009-06-23  
关于对象再生和WeakReference和PhantomReference的区别
http://zhang-xzhi-xjtu.iteye.com/blog/413159
0 请登录后投票
   发表时间:2009-07-20  
java中为什么不提供这样的api,通过传入一个对象的引用,返回一个结构,结构中是:这个对象对应的类在系统中存在多少个实例,每个实例在进程中存在几个引用,引用的变量名是什么,在哪些线程中第几行引用(类似堆栈跟踪)
0 请登录后投票
   发表时间:2009-07-20  
今天才知道原来还有这么多种引用类型的啊。。。请问有关这方面的什么书讲的比较好呢?
0 请登录后投票
   发表时间:2009-07-20  
patrickyao1988 写道
今天才知道原来还有这么多种引用类型的啊。。。请问有关这方面的什么书讲的比较好呢?

thinking in java中介绍 对象的集合 时在介绍持有引用时提到了一些,但是很可惜,不是很详细。。。
0 请登录后投票
   发表时间:2009-09-17   最后修改:2009-09-18
靠,糊涂了.这问题没搞明白.............
0 请登录后投票
   发表时间:2009-09-18   最后修改:2009-09-18

ConcurrentHashMap<String, SoftReference>
刚才自己捣鼓了一下,确实没有什么好办法可以保证当SoftReference被回收时,Key也自动被Remove.
觉得Memory Leak倒不是大问题,因为Key所占内存不多,最主要是Key过多会影响检索的效率.
个人觉得解决这个问题应该很简单:

我们使用SoftReference,必然要在获取SoftReference后需要检查SoftReference.get()是否为空,如果为空,就只有一种可能,在你没有手动Remove的情况下,虚拟机把它回收了,那么回收必然造成有一些无效的key,在这种情况下做一次清理就可以了
注意SoftReference.get()==null不应该频繁出现,除非虚拟机频繁对SoftReference进行回收,如果这样,你或许要检查你程序了。

另外通过继承ConcurrentHashMap 重写put/get几个方法,应该可以把这些操作封装到子类中,还是让子类作为一个Map<K, T>来使用。

感觉直接使用ConcurrentHashMap<String, SoftReference>还是别扭。

 

0 请登录后投票
   发表时间:2009-09-18   最后修改:2009-09-18

无测试无真相,上代码

 

import static org.junit.Assert.*;

import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

import org.junit.Test;

public class TestCase {

	@Test
	public void testWakeConcurrentMap() {
		ReferenceMap wakeMap = new ReferenceMap(WeakReference.class);
		String key = "abc";
		Object value = new Object();
		wakeMap.put(key, value);
		value = null;
		System.gc();

		assertNull(wakeMap.get(key));
		assertTrue(wakeMap.isEmpty());	
	}
	
	@Test
	public void testSoftConcurrentMap() {
		ReferenceMap wakeMap = new ReferenceMap(SoftReference.class);
		String key = "abc";
		Object value = new Object();
		wakeMap.put(key, value);
		value = null;
		System.gc();
		
		assertNotNull(wakeMap.get(key));
		assertFalse(wakeMap.isEmpty());	
	}
}

 

下面是Concurrent的Map子类

import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

public class ReferenceMap extends ConcurrentHashMap {
	private Class<? extends Reference> refClass;

	public ReferenceMap(Class refClass) {
		super();
		this.refClass = refClass;
	}

	public Object put(Object key, Object value) {
		Reference ref = null;
		//这里有点尴尬,没有默认构造器,无法使用反射获得实例...
		if (refClass == SoftReference.class) {
			ref = new SoftReference(value);
		} else if (refClass == WeakReference.class) {
			ref = new WeakReference(value);
		}

		Reference result = (Reference) super.put(key, ref);
		if (result != null) {
			return result.get();
		} else
			return null;
	}

	@Override
	public Object get(Object key) {
		Reference ref = (Reference) super.get(key);
		if (ref.get() == null) {
			cleanOverdueKeys();
			return null;
		}
		return ref.get();
	}

	@Override
	public boolean containsKey(Object key) {
		if (super.containsKey(key)) {
			Reference ref = (Reference) super.get(key);
			if (ref.get() == null) {
				return false;
			} else
				return true;
		} else
			return false;
	}
	
	//清除那些没用的key
	private void cleanOverdueKeys() {
		ArrayList removeList = new ArrayList();
		for (Object key : this.keySet()) {
			if (!containsKey(key))
				removeList.add(key);
		}
		for (Object key : removeList) {
			this.remove(key);
		}
	}
}
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics