开发者社区> curiousby> 正文

WeakHashMap ReferenceQueue java

简介: WeakHashMap,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值, 见实例:此例子中声明了两个Map对象,一个是HashMap,一个是WeakHashMap,同时向两个map中放入a、b两个对象,当HashMap  remove掉a 并且将a、b都指向null时,WeakHashMap中的a将自动被回收掉。
+关注继续查看
WeakHashMap,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值,

见实例:此例子中声明了两个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将会保留。

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的关键代码,非常有参考价值。但是这篇文章遗漏了几个很重要的需要注意的地方,也缺少一段实验代 码,本文将会做出适当补充。
    1、四种引用
    从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
    强引用:平时我们编程的时候例如:Object object=new Object();那object就是一个强引用了。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
    软引用(SoftReference):如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存 空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联 的引用队列中。
    弱引用(WeakReference):如果一个对象只具有弱引用,那就类似于可有可物的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更 短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。  弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联 的引用队列中。
    虚引用(PhantomReference):“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象 仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队 列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
    2、WeakHashMap源码分析
    WeakHashMap维护了一个ReferenceQueue,保存了所有存在引用的Key对象。
    
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;
    }
    ……
    }
 
    WeakHashMap中有一个私有的expungeStaleEntries()方法,会在大部分共有方法中被调用。这个方法会将ReferenceQueue中所有失效的引用从Map中去除。
   
 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;
    }
    }
    }
 
    3、几个需要注意的地方
    WeakHashMap的Key是弱引用,Value不是。
    WeakHashMap不会自动释放失效的弱引用,仅当包含了expungeStaleEntries()的共有方法被调用的时候才会释放

 

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)线程去进行GCConcurrentMarkSweepThread线程被创建的同时会创建一个SurrogateLockerThread(简称SLT)线程并且启动它,SLT启动之后,处于等待阶段。CMST开始GC时,会发一个消息给SLT让它去获取JavaReference对象的全局锁:lock。直到CMS GC完毕之后,JVM会将WeakHashMap中所有被回收的对象所属的WeakReference容器对象放入到Referencepending 属性当中(每次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);
         }
     }
 }                 

 

上面部分讲了JVMGC的时候帮我们把WeakHashMap中的key的内存释放掉了,那么 WeakHashMapEntry数据怎么释放,看看 WeakHashMap ReferenceQueue怎么起的作用? 

Entry如何清理

GC之后,WeakHashMap对象里面getput数据或者调用size方法的时候,WeakHashMapHashMap多了一个 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;
             }
         }
     }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

捐助开发者

在兴趣的驱动下,写一个免费的东西,有欣喜,也还有汗水,希望你喜欢我的作品,同时也能支持一下。 当然,有钱捧个钱场(右上角的爱心标志,支持支付宝和PayPal捐助),没钱捧个人场,谢谢各位。



 
 
 谢谢您的赞助,我会做的更好!

 

 

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,大概有三种登录方式:
9058 0
使用SSH远程登录阿里云ECS服务器
远程连接服务器以及配置环境
12455 0
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
19692 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
32723 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
17986 0
使用NAT网关轻松为单台云服务器设置多个公网IP
在应用中,有时会遇到用户询问如何使单台云服务器具备多个公网IP的问题。 具体如何操作呢,有了NAT网关这个也不是难题。
34554 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
24789 0
windows server 2008阿里云ECS服务器安全设置
最近我们Sinesafe安全公司在为客户使用阿里云ecs服务器做安全的过程中,发现服务器基础安全性都没有做。为了为站长们提供更加有效的安全基础解决方案,我们Sinesafe将对阿里云服务器win2008 系统进行基础安全部署实战过程! 比较重要的几部分 1.
11762 0
+关注
curiousby
echo
428
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
OceanBase 入门到实战教程
立即下载
阿里云图数据库GDB,加速开启“图智”未来.ppt
立即下载
实时数仓Hologres技术实战一本通2.0版(下)
立即下载