hi,大家好
不管是为了工作,还是为了面试,我们都得掌握好ThreadLocal
,下面就来个ThreadLocal四连问:
ThreadLocal
的原理是什么?ThreadLocal
怎么就会导致内存泄漏?OOM
的背锅侠?ThreadLocal
最佳使用方式是怎样的?能搞定上面的四连问,那咱们就能轻松应对工作和面试了。
我们先从代码案例开始,没代码都是耍流氓!
1、首先看一下代码:模拟了一个线程数为THREAD_LOOP_SIZE
的线程池,所有线程共享一个 ThreadLocal 变量,每一个线程执行的时候插入一个大的 List 集合,这里由于执行了500
次循环,也就是产生了500个线程,每一个线程都会依附一个 ThreadLocal 变量:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalOOMDemo {
private static final int THREAD_LOOP_SIZE = 500;
private static final int MOCK_BIG_DATA_LOOP_SIZE = 10000;
private static ThreadLocal<List<User>> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_LOOP_SIZE);
for (int i = 0; i < THREAD_LOOP_SIZE; i++) {
executorService.execute(() -> {
threadLocal.set(new ThreadLocalOOMDemo().addBigList());
Thread t = Thread.currentThread();
System.out.println(Thread.currentThread().getName());
});
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private List<User> addBigList() {
List<User> params = new ArrayList<>(MOCK_BIG_DATA_LOOP_SIZE);
for (int i = 0; i < MOCK_BIG_DATA_LOOP_SIZE; i++) {
params.add(new User("xuliugen", "password" + i, "男", i));
}
return params;
}
class User {
private String userName;
private String password;
private String sex;
private int age;
public User(String userName, String password, String sex, int age) {
this.userName = userName;
this.password = password;
this.sex = sex;
this.age = age;
}
}
}
2、设置 JVM 参数设置最大内存为 256M,以便模拟出 OOM:
参数设置
3、运行代码,输出结果:
[ OOM日志
可以看出,单线程池执行到第212的时候,就报了错误,出现 OOM 内存溢出错误。
4、[在运行代码的时候,同时打开 JDK 工具 jconsole 监控内存变化(可以直接在 cmd 中输入 jconsole 回车调用):]
内存变化
可以看出,上述内存一直递增到 JVM 设置的最大值(大致上),然后抛出异常,程序退出!
5、[这个实例可以很好的演示了:线程池中的每一个线程使用完 ThreadLocal 对象之后,再也不用,由于线程池中的线程不会退出,线程池中的线程的存在,同时 ThreadLocal 变量也会存在,占用内存!造成 OOM 溢出!]
都说ThreadLocal
使用不正当的使用ThreadLocal
造成OOM
的原因,下边详细的介绍一下:
1、首先看一下 ThreadLocal
的原理图:
Thread
、ThreadLocal
、ThreadLocalMap
、Entry
之间的关系:
关系
上图中描述了:一个 Thread 中只有一个 ThreadLocalMap,一个 ThreadLocalMap 中可以有多个 ThreadLocal 对象,其中一个 ThreadLocal 对象对应一个 ThreadLocalMap 中一个的 Entry(也就是说:一个 Thread 可以依附有多个 ThreadLocal 对象)。
在 ThreadLocal 的生命周期中,都存在这些引用。
看下图:实线代表强引用,虚线代表弱引用。
2、ThreadLocal 的实现是这样的:每个 Thread 维护一个 ThreadLocalMap
映射表,这个映射表的 key 是 ThreadLocal
实例本身,value 是真正需要存储的 Object。
3、也就是说 ThreadLocal
本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap
获取 value。值得注意的是图中的虚线,表示 ThreadLocalMap
是使用 ThreadLocal
的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。
4、ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal 没有外部强引用来引用它,那么系统 GC 的时候,这个 ThreadLocal 势必会被回收,这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value,如果当前线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄漏。
5、总的来说就是,ThreadLocal 里面使用了一个存在弱引用的 map,map 的类型是ThreadLocal.ThreadLocalMap.
Map中的 key 为一个 threadlocal 实例。这个 Map 的确使用了弱引用,不过弱引用只是针对 key。每个 key 都弱引用指向 threadlocal。当把 threadlocal 实例置为 null 以后,没有任何强引用指向 threadlocal 实例,所以 threadlocal 将会被 gc 回收。
但是,我们的 value 却不能回收,而这块 value 永远不会被访问到了,所以存在着内存泄露。因为存在一条从current thread
连接过来的强引用。只有当前thread结束以后,current thread
就不会存在栈中,强引用断开,Current Thread、Map value 将全部被 GC 回收。最好的做法是将调用 threadlocal 的 remove 方法,这也是等会后边要说的。
6、其实,ThreadLocalMap 的设计中已经考虑到这种情况,也加上了一些防护措施:在 ThreadLocal 的get(),set(),remove()的时候都会清除线程 ThreadLocalMap 里所有 key 为 null 的 value。这一点在上一节中也讲到过!
7、但是这些被动的预防措施并不能保证不会内存泄漏:
(1)使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致内存泄漏。
(2)分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏,因为这块内存一直存在。
从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析 ThreadLocal 使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?
关于JVM请看:[2万字!JVM核心知识总结,赠送18连环炮]
我们先来看看官方文档的说法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了应对非常大和长时间的用途,哈希表使用弱引用的 key。
下面我们分两种情况讨论:
(1)key 使用强引用:引用的ThreadLocal
的对象被回收了,但是ThreadLocalMap
还持有ThreadLocal
的强引用,如果没有手动删除,ThreadLocal
不会被回收,导致 Entry 内存泄漏。
(2)key 使用弱引用:引用的 ThreadLocal 的对象被回收了,由于ThreadLocalMap
持有ThreadLocal
的弱引用,即使没有手动删除,ThreadLocal
也会被回收。value
在下一次ThreadLocalMap
调用set、get、remove
的时候会被清除。
比较两种情况,我们可以发现:由于ThreadLocalMap
的生命周期跟 Thread 一样长,如果都没有手动删除对应 key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的 value 在下一次ThreadLocalMap调用set、get、remove的时候会被清除。
因此,ThreadLocal 内存泄漏的根源是:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏,而不是因为弱引用。
1、综合上面的分析,我们可以理解 ThreadLocal 内存泄漏的前因后果,那么怎么避免内存泄漏呢?
答案就是:每次使用完 ThreadLocal,都调用它的 remove() 方法,清除数据。
关于线程池:[快速搞定线程池]
在使用线程池的情况下,没有及时清理 ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用 ThreadLocal 就跟加锁完要解锁一样,用完就清理。
注意:
并不是所有使用 ThreadLocal 的地方,都在最后 remove(),他们的生命周期可能是需要和项目的生存周期一样长的,所以要进行恰当的选择,以免出现业务逻辑错误!但首先应该保证的是 ThreadLocal 中保存的数据大小不是很大!
2、那么我们修改最开始的代码为:
取消注释:threadLocal.remove(); 结果不会出现 OOM,可以看出堆内存的变化呈现锯齿状,证明每一次 remove() 之后,ThreadLocal 的内存释放掉了!线程池中的线程的数量持续增加!
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalOOMDemo {
private static final int THREAD_LOOP_SIZE = 500;
private static final int MOCK_BIG_DATA_LOOP_SIZE = 10000;
private static ThreadLocal<List<User>> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_LOOP_SIZE);
for (int i = 0; i < THREAD_LOOP_SIZE; i++) {
executorService.execute(() -> {
threadLocal.set(new ThreadLocalOOMDemo().addBigList());
Thread t = Thread.currentThread();
System.out.println(Thread.currentThread().getName());
threadLocal.remove();
});
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
executorService.shutdown();
}
private List<User> addBigList() {
List<User> params = new ArrayList<>(MOCK_BIG_DATA_LOOP_SIZE);
for (int i = 0; i < MOCK_BIG_DATA_LOOP_SIZE; i++) {
params.add(new User("xuliugen", "password" + i, "男", i));
}
return params;
}
class User {
private String userName;
private String password;
private String sex;
private int age;
public User(String userName, String password, String sex, int age) {
this.userName = userName;
this.password = password;
this.sex = sex;
this.age = age;
}
}
}
取消注释:threadLocal.remove();
结果不会出现 OOM,可以看出堆内存的变化呈现锯齿状,证明每一次remove() 之后,ThreadLocal 的内存释放掉了!线程池中的线程的数量持续增加!
好了,今天的分享就到这里了 。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/I42kEH4aFqelkomVWxFkrg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。