ThreadLocal在执行set()方法的时候,实际执行set()逻辑的是其内部类ThreadLocalMap。

private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();
}

通过nextIndex()不断获取table上得槽位,直到遇到第一个为null的地方,此处也将是存放具体entry的位置,在线性探测法的不断冲突中,如果遇到非空entry中的key为null,可以表明key的弱引用已经被回收,但是由于线程仍未结束生命周期被回收而导致该entry仍未从table中被回收,那么则会在这里尝试通过replaceStaleEntry()方法,将null key的entry回收掉并set相应的值。

private void replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot) {Entry[] tab = table;int len = tab.length;Entry e;int slotToExpunge = staleSlot;for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len))if (e.get() == null)slotToExpunge = i;for (int i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;tab[i] = tab[staleSlot];tab[staleSlot] = e;if (slotToExpunge == staleSlot)slotToExpunge = i;cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);return;}if (k == null && slotToExpunge == staleSlot)slotToExpunge = i;}tab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value);if (slotToExpunge != staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

在replaceStaleEntry()方法中,带有入参staleSlot为已经得知并准备替换的槽位,该方法重点

一共要完成两件事,找到一趟hash下来第一个出现的nul key位置开始回收,第二件事,将staleSlot位置的null key entry替换为目前想要set的entry,当然该key可能已经在table中,需要将其移动大该位置。

第一个部分,将在目标槽位不断向前扫描直到遇到空槽位,在这一趟扫描下,将记录该位置最早出现的null key位置,并记录为slotToExpunge。

slotToExpunge为接下来清理null key的起始位置槽位,一定是一趟线性探测下来最早出现null key的那个槽位。

在向前扫描完成后,将会从staleSlot开始,向后不断获取下一个位置直到遇到空槽位,期间可能会发生三种情况。

第一种,key存在的其他entry,不管,跳过。

第二种,key不存在的null key entry,如果是首次,更新其slotToExpunge,准备作为接下来回收的起始位置,如果第一部分向前扫描的时候已经存在,将不会修改。

第三种,则是找到了本来将要set的key,也就是该次操作将要把该key从现有的位置swap到staleSlot位置,并回收掉staleSlot原本位置上的null key。

在上述第三种情况未发生的情况下,将会新建一个entry发到staleSlot位置。

在完成staleSlot位置的放入后,将会通过expungeStaleEntry()方法从该趟hash中遇到的第一个null key的位置回收,并在之后停过cleanSomeSlots()方法进行一次启发式的回收。

先看expungeStaleEntry()方法。

private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;tab[staleSlot].value = null;tab[staleSlot] = null;size--;Entry e;int i;for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();if (k == null) {e.value = null;tab[i] = null;size--;} else {int h = k.threadLocalHashCode & (len - 1);if (h != i) {tab[i] = null;while (tab[h] != null)h = nextIndex(h, len);tab[h] = e;}}}return i;
}

这个方法主要做了两件事,一件事是从hash下某个值的一轮线性探测的首个null key空键开始向后不断回收,第二件事就是在这趟中的正常键将会进行rehash以便将其移至前序被回收的空槽位上。

在完成一趟hash下的空键回收和rehash之后,将会再次通过cleanSomeSlots()方法进行一次启发式的扫描回收。

private boolean cleanSomeSlots(int i, int n) {boolean removed = false;Entry[] tab = table;int len = tab.length;do {i = nextIndex(i, len);Entry e = tab[i];if (e != null && e.get() == null) {n = len;removed = true;i = expungeStaleEntry(i);}} while ( (n >>>= 1) != 0);return removed;
}

此处的遍历,将会在前一个方法中的最后一个key的下一个hash位置进行扫描并进行垃圾回收,理想情况下的执行次数是log2(散列表长度)次,当遇到null key空键时将会重置扫描次数,这里的次数实现选择是介于不扫描和全表遍历之间的一种选择,尽可能在保证效率的前提下所往后进行扫描。

在上述的整个流程走完后,回顾下最一开始的set()方法最后。

if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();

当最后散列表中的个数大于负载个数后,将会调用rehash()方法进行扩容rehash分配。

private void rehash() {expungeStaleEntries();// Use lower threshold for doubling to avoid hysteresisif (size >= threshold - threshold / 4)resize();
}

在rehash()方法中,共分为两步expungeStaleEntries()方法将对散列表中的所有槽位进行一次遍历,对所有空键调用上述的expungeStaleEntryEntry()方法进行回收。

在回收之后,再次判断是否需要扩容,如果仍旧个数大于负载数量,将会resize(),将会通过resize()进行扩容,重新根据新的长度进行一次散列表的重分配。

threadlocal的set()方法中的内存回收相关推荐

  1. Android 操作系统中的内存回收

    Android 系统中内存回收的触发点大致可分为三种情况: 第一种情况:用户程序调用 StartActivity(), 使当前活动的 Activity 被覆盖 第二种情况:按下Back键,会调用fin ...

  2. JavaScript中的内存回收机制

    JS的内存回收 在js中,垃圾回收器每隔一段时间就会找出那些不再使用的数据,并释放其所占用的内存空间. 以全局变量和局部变量来说,函数中的局部变量在函数执行结束后这些变量已经不再被需要,所以垃圾回收器 ...

  3. 深入理解Java中的内存泄漏

    理解Java中的内存泄漏,我们首先要清楚Java中的内存区域分配问题和内存回收的问题本文将分为三大部分介绍这些内容. Java中的内存分配 Java中的内存区域主要分为线程共享的和线程私有的两大区域: ...

  4. Js内存回收 === 屠龙之术?

    2019独角兽企业重金招聘Python工程师标准>>> 本文可以看做是之前那篇勇士斗恶龙之没那么复杂的Js闭包的后续篇,在思考闭包中内存的问题时,有了写此文的冲动. 学以致用,从实用 ...

  5. linux内存回收(二)--直接内存回收机制

    上一章,我们学习了kswapd的内存回收的机制,其本身是一个内核线程,它和调用者的关系是异步的,那么本章就开始学习内核的内存回收的方式.因为在不同的内存分配路径中,会触发不同的内存回收方式,内存回收针 ...

  6. android 内存回收机制

    Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化, 使得其进程调度与资源管 ...

  7. Android 操作系统的内存回收机制

    转自:http://www.ibm.com/developerworks/cn/opensource/os-cn-android-mmry-rcycl/index.html Android APP 的 ...

  8. Android 操作系统的内存回收机制。

    转载自品略网:http://www.pinlue.com/article/2020/03/0808/089994336918.html Android APP 的运行环境 Android 是一款基于 ...

  9. Android 操作系统的内存回收机制之默认内存回收、OOM以及lowmemorykiller

    Android 操作系统中的内存回收可分为两个层次,即默认内存回收与内核级内存回收,本章重点对默认内存回收机制进行研究,Linux 内核层次的内存回收机制本文不涉及. 本章所有代码可参见 Activi ...

最新文章

  1. ASP.NET操作Word文档(转)
  2. CSS3动画之百度钱包
  3. [220208] Add Digits
  4. C语言 · 9-1九宫格
  5. AWT_Swing_JPasswordField密码框(Java)
  6. 深度学习 arm linux移植过程整理
  7. java面试(葵花宝典)
  8. 互联网+创新创业大赛项目计划书,个人原创你学会了吗?
  9. delphi 7无法运行提示 Borland license information was found, but it is not valid for Delphi
  10. 短视频创业,如何在技术上节省100万启动资金?
  11. “工业互联网+安全生产”,提升工业企业安全水平
  12. BootStrap富文本编辑器Summernote
  13. 阿里范皓宇:互联网汽车会为汽车行业带来全新的用户价值
  14. 德巴赫猜想python_哥德巴赫猜想问题基于Python的验证方法研究
  15. TIOBE 3 月编程语言排行榜刚刚出炉
  16. android 阿拉伯数字转汉字,Android将阿拉伯数字转换为英文数字
  17. MapReduce编程规范及实践(流量统计)
  18. 简练软考知识点整理-项目收尾过程组
  19. 全景分割调研(2) 常用数据集
  20. Spring框架技术总结(一),如何才能更容易拿到大厂Offer

热门文章

  1. Python使用BeautifulSoup爬取网页中主体部分的内容,并导出为pdf格式
  2. Struts2之前台表单传值到后台Action方法总结
  3. prestashop 隐藏 index.php,删除PrestaShop中的供应商和制造商页面
  4. JVM垃圾回收,面试问到的都有了
  5. geoserver rest 导入shape文件错误
  6. [Leetcode]字符串转换整数 (ATOI)
  7. 马哥-Linux云计算架构班学习计划
  8. 日常 Python 编程优雅之道
  9. js基础--变量、数据类型、循环、判断、函数定义
  10. Android 圆角TabLayout