分析ThreadLocal的弱引用与内存泄漏问题
目录
一.介绍
二.问题提出
2.1内存原理图
2.2几个问题
三.回答问题
3.1为什么会出现内存泄漏
3.2若Entry使用弱引用
3.3弱引用配合自动回收
四.总结
一.介绍
之前使用ThreadLocal的时候,就听过ThreadLocal怎么怎么的可能会出现内存泄漏,具体原因也没去深究,就是一种不清不楚的状态。最近在看JDK的源码,其中就包含ThreadLocal,在对ThreadLocal的使用场景介绍以及源码的分析后,对于ThreadLocal中可能存在的内存泄漏问题也搞清楚了,所以这里专门写一篇博客分析一下。
在分析内存泄漏之前,先了解2个概念,就是内存泄漏和内存溢出:
内存溢出(memory overflow):是指不能申请到足够的内存进行使用,就会发生内存溢出,比如出现的OOM(Out Of Memory)
内存泄漏(memory lack):内存泄露是指在程序中已经动态分配的堆内存由于某种原因未释放或者无法释放(已经没有用处了,但是没有释放),造成系统内存的浪费,这种现象叫“内存泄露”。
当内存泄露到达一定规模后,造成系统能申请的内存较少,甚至无法申请内存,最终导致内存溢出,所以内存泄露是导致内存溢出的一个原因。
二.问题提出
2.1内存原理图
下图是程序运行中的内存分布图,简要介绍一下这种图:当前线程有一个threadLocals属性(ThreadLocalMap属性),该map的底层是数组,每个数组元素时Entry类型,Entry类型的key是ThreadLocal类型(也就是创建的ThreadLocal对象),而value是则是ThreadLocal.set()方法设置的value。

需要注意的是ThreadLocalMap的Entry,继承自弱引用,定义如下,关于Java的引用介绍,可以参考:Java-强引用、软引用、弱引用、虚引用
/**
* ThreadLocalMap中存放的元素类型,继承了弱引用类
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
// key对应的value,注意key是ThreadLocal类型
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
2.2问题提出
在看了上面ThreadLocal和ThreadLocalMap相关的内存分布以及关联后,提出这样几个问题:
1.ThreadLocal为什么会出现内存溢出?
2.Entry的key为什么要用弱引用?
3.使用弱引用是否就能解决内存溢出?
为了回答上面这3个问题,我写了一段代码,后面根据这段代码进行分析:
public void step1() {
// some action
step2();
step3();
// other action
}
// 在stepX中都会创建threadLocal对象
public void step2() {
ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("this is value");
}
public void step3() {
ThreadLocal<Integer> tl = new ThreadLocal<>();
tl.set(99);
}
在step1中会调用step2和step3,step2和step3都会创建ThreadLocal对象,当step2和step3执行完毕后,其中的栈内存中ThreadLocal引用就会被清除。
三.回答问题

现在针对这个图,一步一步的分析问题,中途会得出一些临时的结论,但是最终的结论才是正确的。
3.1为什么会出现内存泄露
现在2点假设,本小节的分析都是基于这两个假设之上的:
1.Entry使用强引用,key对ThreadLocal对象时强引用,也就是上面图中连线5是强引用(key强引用ThreadLocal对象);
2.ThreadLocalMap中不会对过期的Entry进行清理。
上面代码中,如果ThreadLocalMap的key使用强引用,那么即使栈内存的ThreadLocal引用被清除,但是堆中的ThreadLocal对象却并不会被清除!!!
这是因为ThreadLocalMap中Entry的key对ThreadLocal对象是强引用,如果当前线程不结束,那么ThreadLocal将会一直存在,对应的内存就不会被回收,与之关联的Entry也不会被回收(Entry对应的value也不会被回收),当这种情况出现数量比较多的时候,未释放的内存就会上升,就可能出现内存泄漏的问题。
上面的结论是暂时的,有前提假设!!!最终结论还需要看后面分析。
3.2若Entry使用弱引用

仍旧有1个假设,就是ThreadLocalMap中不会对过期的Entry进行清理,陈旧的Entry是指Entry的key为null。
按照源码,Entry继承弱引用,其Key对ThreadLocal是弱引用,也就是上图中连线5是弱引用,连线6仍为强引用。
同样以上面代码为例,step2和step3创建了ThreadLocal对象,step2和step3执行完后,栈中的ThreadLocal引用被清除了;由于堆内存中ThreadLocalMap的Entry key弱引用ThreadLocal对象,根据垃圾收集器对弱引用对象的处理:
当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
此时堆中ThreadLocal对象会被gc回收,Entry的key为null,但是value不为null,且value也是强引用(连线6),所以Entry仍旧不能回收,只能释放ThreadLocal的内存,仍旧可能导致内存泄漏。
在没有自动清理陈旧Entry的前提下,即使Entry使用弱引用,仍可能出现内存泄漏。
3.3弱引用配合自动回收
通过3.3的分析,其实只要陈旧的Entry能自动被回收,就能解决内存泄漏的问题,当然JDK就是这么做的。
如果看过源码,就知道,ThreadLocalMap底层使用数组来保存元素,使用“线性探测法”来解决hash冲突,关于线性探测法的介绍可以查看:利用线性探测法解决hash冲突
在每次调用ThreadLocal类的get、set、remove这些方法的时候,内部其实都是对ThreadLocalMap进行操作,对应ThreadLocalMap的get、set、remove操作。
重点来了!重点来了!重点来了!
ThreadLocalMap的每次get、set、remove,都会清理过期的Entry,下面以get操作解释,其他操作也是一个意思,大致如下:
1.ThreadLocalMap底层用数组保存元素,当get一个Entry时,根据key的hash值(非hashCode)计算出该Entry应该出在什么位置;
2.计算出的位置可能会有冲突,比如预期位置是position=5,但是position=5的位置已经有其他Entry了;
3.出现冲突后,会使用线性探测法,找position=6位置上的Entry是否匹配(匹配是指hash相同),如果匹配,则返回position=6的Entry。
4.在这个过程中,如果position=5位置上的Entry已经是陈旧的Entry(Entry的key为null),此时position=5的key就应该被清理;
5.光清理position=5的Entry还不够,为了保证线性探测法的规则,需要判断数组中的其他元素是否需要调整位置(如果需要,则调整位置),在这个过程中,也会进行清理陈旧Entry的操作。
上面这5个步骤就保证了每次get都会清理数组中(map)的陈旧Entry,清理一个陈旧的Entry,就是下面这三行代码:
Entry.value = null; // 将Entry的value设为null
table[index] = null;// 将数组中该Entry的位置设置null
size--; // map的size减一
对于ThreadLocal的set、remove也类似这个原理。
有了自动回收陈旧Entry的操作,需要注意的是,在这个时候,key使用弱引用就是至关重要的一点!!!
因为key使用弱引用后,当弱引用的ThreadLocal对象被会回收后,该key的引用为null,则该Entry在下一次get、set、remove的时候就才会被清理,从未避免内存泄漏的问题。
四.总结
在上面的分析中,看到ThreadLocal基本不会出现内存泄漏的问题了,因为ThreadLocalMap中会在get、set、remove的时候清理陈旧的Entry,与Entry的key使用弱引用密不可分。
当然我们也可以在代码中手动调用ThreadLocal的remove方法进行清除map中中key为该threadLocal对象的Entry,同时清理过期的Entry。
分析ThreadLocal的弱引用与内存泄漏问题的更多相关文章
- Java 理论与实践: 用弱引用堵住内存泄漏---转载
要让垃圾收集(GC)回收程序不再使用的对象,对象的逻辑 生命周期(应用程序使用它的时间)和对该对象拥有的引用的实际 生命周期必须是相同的.在大多数时候,好的软件工程技术保证这是自动实现的,不用我们对对 ...
- Java 理论与实践: 用弱引用堵住内存泄漏
弱引用使得表达对象生命周期关系变得容易了 虽然用 Java™ 语言编写的程序在理论上是不会出现“内存泄漏”的,但是有时对象在不再作为程序的逻辑状态的一部分之后仍然不被垃圾收集.本月,负责保障应用程序健 ...
- android WeakReference(弱引用 防止内存泄漏)与SoftReference(软引用 实现缓存机制(cache))
在Android开发中,基本上很少有用到软引用或弱引用,这两个东东若用的很好,对自己开发的代码质量的提高有很大的帮助.若用的不好,会坑了自己.所以,在还没有真正的去了解它们之前,还是慎用比较好. 下面 ...
- 九、Android学习笔记_ Android开发中使用软引用和弱引用防止内存溢出
在<Effective Java 2nd Edition>中,第6条“消除过期的对象引用”提到,虽然Java有 垃圾回收机制,但是只要是自己管理的内存,就应该警惕内存泄露的问题,例如的对象 ...
- Android学习笔记_78_ Android开发中使用软引用和弱引用防止内存溢出
在<Effective Java 2nd Edition>中,第6条“消除过期的对象引用”提到,虽然Java有 垃圾回收机制,但是只要是自己管理的内存,就应该警惕内存泄露的问题,例如的对象 ...
- Hanlder + 弱引用防内存漏泄示例*
Hanlder + 弱引用防内存漏泄示例: public class MainActivity extends AppCompatActivity { public final MyHandler h ...
- Android性能优化之巧用软引用与弱引用优化内存使用
前言: 从事Android开发的同学都知道移动设备的内存使用是非常敏感的话题,今天我们来看下如何使用软引用与弱引用来优化内存使用.下面来理解几个概念. 1.StrongReference(强引用) 强 ...
- 记一次 WinDbg 分析 .NET 某工厂MES系统 内存泄漏分析
一:背景 1. 讲故事 上个月有位朋友加微信求助,说他的程序跑着跑着就内存爆掉了,寻求如何解决,截图如下: 从聊天内容看,这位朋友压力还是蛮大的,话说这貌似是我分析的第三个 MES 系统了,看样子 . ...
- Android开发过程中使用弱引用解决内存泄露的习惯
Java虽然有垃圾回收,但是仍然存在内存泄露,比如静态变量.缓存或其他长生命周期的对象引用了其他对象,这些被引用的对象就会长期不能被GC释放,导致内存泄露. 弱引用(WeakReference)是解决 ...
随机推荐
- 项目中 SimpleDateFormat 的正确使用
项目中 SimpleDateFormat 的正确使用 日常开发中,我们经常需要使用时间相关类,说到时间相关类,想必大家对 SimpleDateFormat 并不陌生.主要是用它进行时间的格式化输出和解 ...
- WordPress安全 - 隐藏保护wp-login.php后台登陆入口
我们在基本的设置账户用户名和密码安全基础上,最好把这个登录入口限制访问或者隐藏,之前也有看到一些教程说安装插件,比如安装Stealth Login Page插件可以设置登录页面后的参数,与我要设置的非 ...
- Java 在PPT中创建SmartArt图形、读取SmartArt图形中的文本
一.概述及环境准备 SmartArt 图形通过将文字.图形从多种不同布局.组合来表现内容和观点的逻辑关系,能够快速.有效地传达设计者的意图和信息.这种图文表达的视觉表示形式常用于PPT,Word,Ex ...
- Android_四大组件之BroadcastReceiver
一.概述 BroadcastReceiver是广播接收器,接收来自 系统或应用发出的广播信息 并进行相应的逻辑处理. 自定义BroadcastReceiver只需继承android.content.B ...
- DNS域传输漏洞利用总结
操作基本的步骤是: 1) 输入nslookup命令进入交互式shell 2) Server 命令参数设定查询将要使用的DNS服务器 3) Ls命令列出某个域中的所有域名 4) Exit命令退出程序 攻 ...
- Matlab矩阵学习二 矩阵的修改
Matlab矩阵的修改 一.元素修改 (1).矩阵扩充 (2)矩阵删除某行或某列 删除某行:A(m,:)=[] %删除A矩阵的第m行 删除某列: A(:,n)=[] %删除A矩阵的第n列 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(三)
上一篇(https://www.cnblogs.com/meowv/p/12974439.html)完成了全网各大平台的热点新闻数据的抓取,本篇继续围绕抓取完成后的操作做一个提醒.当每次抓取完数据后, ...
- bypass disable_function的方法及蚁剑插件bypass-php-function使用
bypass disable_function的方法及蚁剑插件bypass-php-function使用 在学习php时,发现有许多函数会对网站或系统造成很大危险隐患,常见的危险函数有: phpinf ...
- 「MoreThanJava」一文了解二进制和CPU工作原理
「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...
- Java实现 蓝桥杯 算法提高 小X的购物计划
试题 算法提高 小X的购物计划 问题描述 小X打算去超市shopping.小X没什么钱,只有N元.超市里有M种物品,每种物品都需要money,在小X心中有一个重要度.有的物品有无限件,有的物品只有几件 ...