浅析WeakHashMap

在Java或者是Android编程中,我们一般都会使用到Map,比如HashMap这样的具体实现。更高级一点,我们可能会使用WeakHashMap。

WeakHashMap其实和HashMap大多数行为是一样的,只是WeakHashMap不会阻止GC回收key对象(不是value),那么WeakHashMap是怎么做到的呢,这就是我们研究的主要问题。

在开始WeakHashMap之前,我们先要对弱引用有一定的了解。

在Java中,有四种引用类型

  • 强引用(Strong Reference),我们正常编码时默认的引用类型,强应用之所以为强,是因为如果一个对象到GC Roots强引用可到达,就可以阻止GC回收该对象
  • 软引用(Soft Reference)阻止GC回收的能力相对弱一些,如果是软引用可以到达,那么这个对象会停留在内存更时间上长一些。当内存不足时垃圾回收器才会回收这些软引用可到达的对象
  • 弱引用(WeakReference)无法阻止GC回收,如果一个对象时弱引用可到达,那么在下一个GC回收执行时,该对象就会被回收掉。
  • 虚引用(Phantom Reference)十分脆弱,它的唯一作用就是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁

这其中还有一个概念叫做引用队列(Reference Queue)

  • 一般情况下,一个对象标记为垃圾(并不代表回收了)后,会加入到引用队列。
  • 对于虚引用来说,它指向的对象会只有被回收后才会加入引用队列,所以可以用作记录该引用指向的对象是否回收。

WeakHashMap如何不阻止对象回收呢

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

private static final class Entry<K, V> extends WeakReference<K> implements Map.Entry<K, V> { int hash; boolean isNull; V value; Entry<K, V> next; interface Type<R, K, V> { R get(Map.Entry<K, V> entry); } Entry(K key, V object, ReferenceQueue<K> queue) { super(key, queue); isNull = key == null; hash = isNull ? 0 : key.hashCode(); value = object; }

如源码所示,

  • WeakHashMap的Entry继承了WeakReference。
  • 其中Key作为了WeakReference指向的对象
  • 因此WeakHashMap利用了WeakReference的机制来实现不阻止GC回收Key

如何删除被回收的key数据呢

在Javadoc中关于WeakHashMap有这样的描述,当key不再引用时,其对应的key/value也会被移除。

那么是如何移除的呢,这里我们通常有两种假设策略

  • 当对象被回收的时候,进行通知
  • WeakHashMap轮询处理时效的Entry

而WeakHashMap采用的是轮询的形式,在其put/get/size等方法调用的时候都会预先调用一个poll的方法,来检查并删除失效的Entry

1 2 3 4 5 6 7

void poll() { Entry<K, V> toRemove; while ((toRemove = (Entry<K, V>) referenceQueue.poll()) != null) { removeEntry(toRemove); Log.d(LOGTAG, "removeEntry=" + toRemove.value); } }

为什么没有使用看似更好的通知呢,我想是因为在Java中没有一个可靠的通知回调,比如大家常说的finalize方法,其实也不是标准的,不同的JVM可以实现不同,甚至是不调用这个方法。

当然除了单纯的看源码,进行合理的验证是检验分析正确的一个重要方法。

这里首先,我们定义一个MyObject类,处理一下finalize方法(在我的测试机上可以正常调用,仅仅做为辅助验证手段)

1 2 3 4 5

class MyObject(val id: String) : Any() { protected fun finalize() { Log.i("MainActivity", "Object($id) finalize method is called") } }

然后是调用者的代码,如下

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

private val weakHashMap = WeakHashMap<Any, Int>() var count : Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) dumpWeakInfo() fab.setOnClickListener { view -> //System.gc()// this seldom works use Android studio force gc stop weakHashMap.put(MyObject(count.toString()), count) count ++ dumpWeakInfo() Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show() } } fun dumpWeakInfo() { Log.i("MainActivity", "dumpWeakInfo weakInfo.size=${weakHashMap.size}") }

我们按照如下操作

  • 点击fab控件,每次对WeakhashMap对象增加一个Entry,并打印WeakHashMap的size 执行3此
  • 在没有强制触发GC时,WeakHashMap对象size一直会增加
  • 手动出发Force GC,我们会看到MyObject有finalize方法被调用
  • 再次点击fab空间,然后输出的WeakHashMap size急剧减少。
  • 同样我们收到在WeakHashMap增加的日志也会输出

1 2 3 4 5 6 7 8 9 10

I/MainActivity(10202): dumpWeakInfo weakInfo.size=1 I/MainActivity(10202): dumpWeakInfo weakInfo.size=2 I/MainActivity(10202): dumpWeakInfo weakInfo.size=3 I/MainActivity(10202): Object(2) finalize method is called I/MainActivity(10202): Object(1) finalize method is called I/MainActivity(10202): Object(0) finalize method is called I/WeakHashMap(10202): removeEntry=2 I/WeakHashMap(10202): removeEntry=0 I/WeakHashMap(10202): removeEntry=1 I/MainActivity(10202): dumpWeakInfo weakInfo.size=1

注意:System.gc()并不一定可以工作,建议使用Android Studio的Force GC

完整的测试代码可以访问这里 https://github.com/androidyue/WeakHashMapSample

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

聊聊storm的window trigger

storm-core-1.2.2-sources.jar!/org/apache/storm/trident/windowing/WindowTridentPr...

13500
来自专栏曾大稳的博客

Glide v3.7源码分析(2)-----RequestManager.load

可以看到,Glide初始化的时候做了很多的事,初始化了缓存相关的类,任务执行以及缓存管理的引擎,注册了DataLoadProviderRegistry Gene...

23810
来自专栏Java编程技术

Spring中RequestScope作用域Bean原理

可知上面时序图完成了对RequestScope对象定义的修改创建了代理bean,具体修改内容是修改了beanClass为ScopedProxyFactoryBe...

34520
来自专栏曾大稳的博客

Android自定义GLSurfaceView

当我们需要把同一个场景渲染到不同的Surface上时,此时系统GLSurfaceView 就不能满足需求了,所以我们需要自己创建EGL环境来实现渲染操作。 注意...

41720
来自专栏码匠的流水账

聊聊storm的window trigger

storm-core-1.2.2-sources.jar!/org/apache/storm/trident/windowing/WindowTridentPr...

10840
来自专栏Android相关

CoordiantorLayout与Behavior

CoordinatorLayout继承自FrameLayout,并且实现了NestedScrollingParent2接口用于接收嵌套滑动的事件。并且内部定义了...

10320
来自专栏Phoenix的Android之旅

如何方便的收集app崩溃日志

很多人可能没了解过这个东西可以干嘛用, 其实它的作用是可以传入一个 Handler来捕获那些没有被捕获的异常, 比如 app 层面的 crash。 下面提供了一...

13520
来自专栏林冠宏的技术文章

java 大数据处理类 BigDecimal 解析

这两天,由于我的必修课概率论里经常要用到排列组合的计算,感觉很麻烦,加上现代智能手机的计算器是没有这方面功能的。 所以,就自己动手写了个安卓的 排列组合 计算器...

219100
来自专栏曾大稳的博客

Android ClassLoader流程解读并简单方式实现热更新

ClassLoader在启动Activity的时候会调用loadClass方法,我们就从这里入手:

31420
来自专栏Android知识点总结

看得见的数据结构Android版之双链表篇

7810

扫码关注云+社区

领取腾讯云代金券