见实例:此例子中声明了两个Map对象,一个是HashMap,一个是WeakHashMap,同时向两个map中放入a、b两个对象,当HashMap? remove掉a 并且将a、b都指向null时,WeakHashMap中的a将自动被回收掉。出现这个状况的原因是,对于a对象而言,当HashMap? remove掉并且将a指向null后,除了WeakHashMap中还保存a外已经没有指向a的指针了,所以WeakHashMap会自动舍弃掉a,而对于b对象虽然指向了null,但HashMap中还有指向b的指针,所以WeakHashMap将会保留。
class="java" name="code">package test;  
  
import java.util.HashMap;  
import java.util.Iterator;  
import java.util.Map;  
import java.util.WeakHashMap;  
  
public class Test {  
    public static void main(String[] args) throws Exception {  
        String a = new String("a");  
        String b = new String("b");  
        Map weakmap = new WeakHashMap();  
        Map map = new HashMap();  
        map.put(a, "aaa");  
        map.put(b, "bbb");  
  
          
        weakmap.put(a, "aaa");  
        weakmap.put(b, "bbb");  
          
        map.remove(a);  
          
        a=null;  
        b=null;  
          
        System.gc();  
        Iterator i = map.entrySet().iterator();  
        while (i.hasNext()) {  
            Map.Entry en = (Map.Entry)i.next();  
            System.out.println("map:"+en.getKey()+":"+en.getValue());  
        }  
  
        Iterator j = weakmap.entrySet().iterator();  
        while (j.hasNext()) {  
            Map.Entry en = (Map.Entry)j.next();  
            System.out.println("weakmap:"+en.getKey()+":"+en.getValue());  
              
        }  
    }  
  
      
} 
?
?
?
在《Effective Java 2nd Edition》中,第6条“消除过期的对象引用”提到,虽然Java有 垃圾回收机制,但是只要是自己管理的内存,就应该警惕内存泄露的问题,例如的对象池、缓存中的过期对象都有可能引发内存泄露的问题。书中还提到可以用 WeakHashMap来作为缓存的容器可以有效解决这一问题。之前也确实遇到过类似问题,但是没有接触过“弱引用”相关的问题,于是查阅了一些资料。??? 《Java 理论与实践: 用弱引用堵住内存泄漏》一文也指出了使用全局的Map作为缓存容器时发生的内存泄露问题,介绍了如何使用hprof工具来找出内存泄露,并分析了如何使用 弱引用来防止内存泄露,还分析了WeakHashMap的关键代码,非常有参考价值。但是这篇文章遗漏了几个很重要的需要注意的地方,也缺少一段实验代 码,本文将会做出适当补充。private final ReferenceQueue<K> queue = new ReferenceQueue<K>();
    WeakHashMap.Entry<K,V>中并没有保存Key,只是将Key与ReferenceQueue关联上了。
    private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V> {
    private V value;
    private final int hash;
    private Entry<K,V> next;
    Entry(K key, V value, ReferenceQueue<K> queue, int hash, Entry<K,V> next) {
    super(key, queue);
    this.value = value;
    this.hash  = hash;
    this.next  = next;
    }
    ……
    }
? private void expungeStaleEntries() {
    Entry<K,V> e;
    while ( (e = (Entry<K,V>) queue.poll()) != null) {
    int h = e.hash;
    int i = indexFor(h, table.length);
    Entry<K,V> prev = table[i];
    Entry<K,V> p = prev;
    while (p != null) {
    Entry<K,V> next = p.next;
    if (p == e) {
    if (prev == e)
    table[i] = next;
    else
    prev.next = next;
    e.next = null;  // Help GC
    e.value = null; //  "   "
    size--;
    break;
    }
    prev = p;
    p = next;
    }
    }
    }
??
Reference类中有一段static代码?
static private class Lock { };
 private static Lock lock = new Lock();  
 private static Reference pending = null;
          
 static {
     ThreadGroup tg = Thread.currentThread().getThreadGroup();
     for (ThreadGroup tgn = tg;
          tgn != null;
          tg = tgn, tgn = tg.getParent());
         Thread handler = new ReferenceHandler(tg, "Reference Handler");
         /* If there were a special system-only priority greater than
          * MAX_PRIORITY, it would be used here
          */
         handler.setPriority(Thread.MAX_PRIORITY);
         handler.setDaemon(true);
         handler.start();
 }
?
线程的优先级设成MAX,是一个什么样的线程需要如此高的权限?pending?、lock?都被static声明,lock.wait之后谁来唤醒,互联网上一顿搜罗,才明白JVM参与了这些事。? ?
用通俗的话把JVM干的事串一下:? ?假设,WeakHashMap对象里面已经保存了很多对象的引用。JVM使用进行CMS GC的时候,会创建一个ConcurrentMarkSweepThread(简称CMST)线程去进行GC,ConcurrentMarkSweepThread线程被创建的同时会创建一个SurrogateLockerThread(简称SLT)线程并且启动它,SLT启动之后,处于等待阶段。CMST开始GC时,会发一个消息给SLT让它去获取Java层Reference对象的全局锁:lock。直到CMS GC完毕之后,JVM会将WeakHashMap中所有被回收的对象所属的WeakReference容器对象放入到Reference的pending?属性当中(每次GC完毕之后,pending属性基本上都不会为null了),然后通知SLT释放并且notify全局锁:?lock。此时激活了ReferenceHandler线程的run方法,使其脱离wait状态,开始工作了。ReferenceHandler这个线程会将pending中的所有WeakReference对象都移动到它们各自的列队当中,比如当前这个WeakReference属于某个WeakHashMap对象,那么它就会被放入相应的ReferenceQueue列队里面(该列队是链表结构)。? ? ? ?
想要了解具体细节,再深扒一下openjdk的源码instanceRefKlass.cpp获得lock部分
?
void instanceRefKlass::acquire_pending_list_lock(BasicLock *pending_list_basic_lock) { 
   // we may enter this with pending exception set 
   PRESERVE_EXCEPTION_MARK;  // exceptions are never thrown, needed for TRAPS argument 
   Handle h_lock(THREAD, java_lang_ref_Reference::pending_list_lock()); 
   ObjectSynchronizer::fast_enter(h_lock, pending_list_basic_lock, false, THREAD); 
   assert(ObjectSynchronizer::current_thread_holds_lock( 
            JavaThread::current(), h_lock), 
          "Locking should have succeeded"); 
   if (HAS_PENDING_EXCEPTION) CLEAR_PENDING_EXCEPTION; 
 }
?
Gc完成后,?pending赋值,lock释放
void instanceRefKlass::release_and_notify_pending_list_lock( 
   BasicLock *pending_list_basic_lock) { 
   // we may enter this with pending exception set 
   PRESERVE_EXCEPTION_MARK;  // exceptions are never thrown, needed for TRAPS argument 
   // 
   Handle h_lock(THREAD, java_lang_ref_Reference::pending_list_lock()); 
   assert(ObjectSynchronizer::current_thread_holds_lock( 
            JavaThread::current(), h_lock), 
          "Lock should be held"); 
   // Notify waiters on pending lists lock if there is any reference. 
   if (java_lang_ref_Reference::pending_list() != NULL) { 
     ObjectSynchronizer::notifyall(h_lock, THREAD); 
   } 
   ObjectSynchronizer::fast_exit(h_lock(), pending_list_basic_lock, THREAD); 
   if (HAS_PENDING_EXCEPTION) CLEAR_PENDING_EXCEPTION; 
 }
?
lock释放后,?ReferenceHandler线程进入正常运转,将?pending?中的Reference对象压入了各自的?ReferenceQueue中? private static class ReferenceHandler extends Thread {
     ReferenceHandler(ThreadGroup g, String name) {
         super(g, name);
     }
     public void run() {
         for (;;) {
         Reference r;
         synchronized (lock) {
             if (pending != null) {
             r = pending;
             Reference rn = r.next;
             pending = (rn == r) ? null : rn;
             r.next = r;
             } else {
             try {
                 lock.wait();
             } catch (InterruptedException x) { }
             continue;
             }
         }
         // Fast path for cleaners
         if (r instanceof Cleaner) {
             ((Cleaner)r).clean();
             continue;
         }
         ReferenceQueue q = r.queue;
         if (q != ReferenceQueue.NULL) q.enqueue(r);
         }
     }
 }                 
?
上面部分讲了JVM在GC的时候帮我们把WeakHashMap中的key的内存释放掉了,那么?WeakHashMap中Entry数据怎么释放,看看?WeakHashMap的?ReferenceQueue怎么起的作用??
当GC之后,WeakHashMap对象里面get、put数据或者调用size方法的时候,WeakHashMap比HashMap多了一个?expungeStaleEntries()方法
private void expungeStaleEntries() {
     Entry<K,V> e;
         while ( (e = (Entry<K,V>) queue.poll()) != null) {
             int h = e.hash;
             int i = indexFor(h, table.length);
             Entry<K,V> prev = table[i];
             Entry<K,V> p = prev;
             while (p != null) {
                 Entry<K,V> next = p.next;
                 if (p == e) {
                     if (prev == e)
                         table[i] = next;
                     else
                         prev.next = next;
                     e.next = null;  // Help GC
                     e.value = null; //  "   "
                     size--;
                     break;
                 }
                 prev = p;
                 p = next;
             }
         }
     }
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?