当前位置:首页 > 科技  > 软件

阿里二面:ThreadLocal内存泄露灵魂四问,人麻了!

来源: 责编: 时间:2023-11-03 09:17:09 204观看
导读ThreadLocal能够在线程本地存储对应的变量,从而有效的避免线程安全问题。但是使用ThreadLocal时,稍微不注意就有可能造成内存泄露的问题。那么ThreadLocal在哪些场景下会出现内存泄露?哪些场景下不会出现内存泄露?出现内

ThreadLocal能够在线程本地存储对应的变量,从而有效的避免线程安全问题。但是使用ThreadLocal时,稍微不注意就有可能造成内存泄露的问题。那么ThreadLocal在哪些场景下会出现内存泄露?哪些场景下不会出现内存泄露?出现内存泄露的根本原因又是什么呢?如何真正避免内存泄露?4pR28资讯网——每日最新资讯28at.com

这可能是你职业生涯中最具含金量的一次点击,点击【项目实战】查看详情,与冰河一起研发基于大厂真正核心技术的硬核项目。4pR28资讯网——每日最新资讯28at.com

接下来,我们就用大量的图解来分析ThreadLocal内存泄露的四个核心问题:哪些场景不会内存泄露、哪些场景会内存泄露、内存泄露的根本原因是什么、以及如何真正 避免内存泄露。4pR28资讯网——每日最新资讯28at.com

一、ThreadLocal内部结构

为了更好的说明ThreadLocal内存泄露的场景,以及具体的原因,先来了解下ThreadLocal的内部结构,如图1所示。4pR28资讯网——每日最新资讯28at.com

图片图片4pR28资讯网——每日最新资讯28at.com

可以看到,ThreadLocal对象是存储在每个Thread线程内部的ThreadLocalMap中的,并且在ThreadLocalMap中有一个Entry数组,Entry数组中的每一个元素都是一个Entry对象。4pR28资讯网——每日最新资讯28at.com

每个Entry对象中存储着一个ThreadLocal对象与其对应的value值,每个Entry对象在Entry数组中的位置是通过ThreadLocal对象的threadLocalHashCode计算出来的,以此来快速定位Entry对象在Entry数组中的位置。所以,在Thread中,可以存储多个ThreadLocal对象。4pR28资讯网——每日最新资讯28at.com

二、不会出现内存泄露的场景

了解完ThreadLocal的内部存储结构后,我们先来思考下哪些场景下ThreadLocal不会发生内存泄露,假设我们单独开启一个线程,并且将变量存储到ThreadLocal中,如图2所示。4pR28资讯网——每日最新资讯28at.com

图片图片4pR28资讯网——每日最新资讯28at.com

可以看到,Thread线程在正常执行的情况下,会引用ThreadLocalMap的实例对象,只要Thread线程一直在执行任务,这种引用关系就一直存在。4pR28资讯网——每日最新资讯28at.com

当Thread线程执行任务结束退出时,Thread线程与ThreadLocalMap实例对象之间的引用关系就不存在了,如图3所示。4pR28资讯网——每日最新资讯28at.com

图片图片4pR28资讯网——每日最新资讯28at.com

Thread线程执行完任务退出后,线程里持有的ThreadLocalMap对象也就失去了强引用,此时ThreadLocalMap对象就会被GC自动回收,而ThreadLocalMap中包含的ThreadLocal对象也会被GC回收掉,如图4所示。4pR28资讯网——每日最新资讯28at.com

图片图片4pR28资讯网——每日最新资讯28at.com

可以看出,如果只是通过Thread类或者Thread类的子类来创建线程执行任务,随着对应线程的任务执行完毕,线程退出,Thread线程引用的ThreadLocal也会被GC回收掉,此时就不会出现内存泄露的问题。4pR28资讯网——每日最新资讯28at.com

三、会出现内存泄露的场景

在实际项目中,如果为每个任务的执行都开启一个线程的话,是非常耗费系统资源的,所以,在实际项目中,我们很少直接使用Thread类来创建线程,而是使用线程池来执行对应的任务。如果是在线程池场景下,线程与ThreadLocalMap之间的引用关系又是怎样的呢?4pR28资讯网——每日最新资讯28at.com

这里,我们先来看一张图,如图5所示。4pR28资讯网——每日最新资讯28at.com

图片图片4pR28资讯网——每日最新资讯28at.com

可以看到,线程池中会有多个线程执行任务,如果是通过ThreadLocal存储数据的话,每个线程都会引用一个ThreadLocalMap对象。4pR28资讯网——每日最新资讯28at.com

另外,线程池中的核心线程在执行完任务后,是不会退出的,可以循环使用,说明线程池中的每个核心线程和ThreadLocalMap之间一直是强引用关系,核心线程对应的ThreadLocal是不会自动被GC回收的,会存在内存泄露的风险。4pR28资讯网——每日最新资讯28at.com

四、内存泄露问题分析

这里,我们对在线程池中使用ThreadLocal存在内存泄露问题的原因进行分析,首先,将ThreadLocalMap中的Entry数组展开,如图6所示。4pR28资讯网——每日最新资讯28at.com

图片图片4pR28资讯网——每日最新资讯28at.com

可以看到,ThreadLocalMap中包含一个Entry数组,而Entry数组中的每一个元素就是Entry对象,Entry对象中存储的Key就是ThreadLocal对象,而value就是要存储的数据。其中,Entry对象中的Key属于弱引用,这点我们可以从ThreadLocalMap类中的内部类Entry的定义可以看出。Entry类的源码详见:java.lang.ThreadLocal.ThreadLocalMap.Entry。4pR28资讯网——每日最新资讯28at.com

static class Entry extends WeakReference<ThreadLocal<?>> {    /** The value associated with this ThreadLocal. */    Object value;    Entry(ThreadLocal<?> k, Object v) {        super(k);        value = v;    }}

可以看到,Entry类继承了WeakReference类,WeakReference类的泛型是ThreadLocal,,说明ThreadLocalMap中的Entry数组对Entry对象的Key就是弱引用。所以,Entry对象中的Key可以被GC自动回收。当Entry对象中的Key被GC自动回收后如图7所示。4pR28资讯网——每日最新资讯28at.com

图片图片4pR28资讯网——每日最新资讯28at.com

当Entry对象中的Key被GC自动回收后,对应的ThreadLocal被GC回收掉了,变成了null,但是ThreadLocal对应的value值依然被Entry引用,不能被GC自动回收,如图8所示。4pR28资讯网——每日最新资讯28at.com

图片图片4pR28资讯网——每日最新资讯28at.com

此时,我们可以看到,Entry对象中的Key,也就是ThreadLocal对象可以被GC自动回收,但是对应的value还在被引用,所以,value是不能被GC自动回收的,这种情况下就会存在内存泄露的风险。4pR28资讯网——每日最新资讯28at.com

我们再来总结下,在线程池中使用ThreadLocal保存数据存在内存泄露风险的原因:线程池中的核心线程会被循环使用,每个线程中对应的ThreadLocalMap会被线程强引用。4pR28资讯网——每日最新资讯28at.com

所以,每个线程对应的ThreadLocalMap不能被GC自动回收。而ThreadLocalMap中包含一个Entry数组,Entry数组中含有多个Key为ThreadLocal,value为存储的数据的Entry对象,虽然Entry对象中的Key是弱引用,能够被GC自动回收,但是value却是强引用,不能被GC自动回收,所以,在线程池中使用ThreadLocal会存在内存泄露的风险。4pR28资讯网——每日最新资讯28at.com

五、如何避免内存泄露

在线程池中使用ThreadLocal如何避免内存泄露呢?ThreadLocal提供相应的解决方法了吗?这里,我们就从ThreadLocal的源码中看看ThreadLocal是否提供了对应的解决方案。4pR28资讯网——每日最新资讯28at.com

在ThreadLocal中,提供了一个remove()方法,源码详见:java.lang.ThreadLocal#remove。4pR28资讯网——每日最新资讯28at.com

public void remove() {    ThreadLocalMap m = getMap(Thread.currentThread());    if (m != null)        m.remove(this);}

可以看到,在remove()方法中,首先根据当前线程获取ThreadLocalMap类型的m对象,不为空,则直接调用m对象的有参remove()方法移除value的值。4pR28资讯网——每日最新资讯28at.com

有参remove()方法的源码详见:java.lang.ThreadLocal.ThreadLocalMap#remove。4pR28资讯网——每日最新资讯28at.com

private void remove(ThreadLocal<?> key) {    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)]) {        if (e.get() == key) {            e.clear();            expungeStaleEntry(i);            return;        }    }}

可以看到,在有参remove()方法中,会通过threadLocalHashCode计算出Entry对象在Entry数组中的位置,并获取出对应的Entry对象,如果Entry对象不为空,并且Entry对象中的Key等于传入的ThreadLocal对象,则清除对应的Key,并且调用expungeStaleEntry()方法。4pR28资讯网——每日最新资讯28at.com

接下来,我们再分析下expungeStaleEntry()方法,源码详见:java.lang.ThreadLocal.ThreadLocalMap#expungeStaleEntry。4pR28资讯网——每日最新资讯28at.com

private int expungeStaleEntry(int staleSlot) {    Entry[] tab = table;    int len = tab.length;    // expunge entry at staleSlot    tab[staleSlot].value = null;    tab[staleSlot] = null;    size--;    // Rehash until we encounter null    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;                // Unlike Knuth 6.4 Algorithm R, we must scan until                // null because multiple entries could have been stale.                while (tab[h] != null)                    h = nextIndex(h, len);                tab[h] = e;            }        }    }    return i;}

可以看到,在expungeStaleEntry()方法中,会将ThreadLocal为null对应的value设置为null,同时会把对应的Entry对象也设置为null,并且会将所有ThreadLocal对应的value为null的Entry对象设置为null,这样就去除了强引用,便于后续的GC进行自动垃圾回收,也就避免了内存泄露的问题。调用ThreadLocal的remove()方法后的示意图如图9所示。4pR28资讯网——每日最新资讯28at.com

图片图片4pR28资讯网——每日最新资讯28at.com

注意:在ThreadLocal中,不仅仅是remove()方法会调用expungeStaleEntry()方法,在set()方法和get()方法中也可能会调用expungeStaleEntry()方法来清理数据。4pR28资讯网——每日最新资讯28at.com

还有一点需要注意的是,ThreadLocal虽然提供了避免内存泄露的方法,但是ThreadLocal不会主动去执行这些方法,需要我们在使用完ThreadLocal对象中保存的数据后,在finally{}代码块中调用ThreadLocal的remove()方法,加快GC自动垃圾回收,避免内存泄露。4pR28资讯网——每日最新资讯28at.com

六、总结

本文,主要结合图例介绍了ThreadLocal有关内存泄露方面的知识,包括:ThreadLocal的内部结构,不会出现内存泄露的场景,会出现内存泄露的场景,内存泄露的问题分析以及如何避免内存泄露。4pR28资讯网——每日最新资讯28at.com

4pR28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-16750-0.html阿里二面:ThreadLocal内存泄露灵魂四问,人麻了!

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 分布式基础 - ZAB协议&amp;负载均衡策略

下一篇: 基于 Web Components 的微前端框架

标签:
  • 热门焦点
  • K60至尊版狂暴引擎2.0加持:超177万跑分斩获性能第一

    K60至尊版狂暴引擎2.0加持:超177万跑分斩获性能第一

    Redmi的后性能时代战略发布会今天下午如期举办,在本次发布会上,Redmi公布了多项关于和联发科的深度合作,以及新机K60 Ultra在软件和硬件方面的特性,例如:“K60 至尊版,双芯旗舰
  • Raft算法:保障分布式系统共识的稳健之道

    Raft算法:保障分布式系统共识的稳健之道

    1. 什么是Raft算法?Raft 是英文”Reliable、Replicated、Redundant、And Fault-Tolerant”(“可靠、可复制、可冗余、可容错”)的首字母缩写。Raft算法是一种用于在分布式系统
  • 雅柏威士忌多款单品价格大跌,泥煤顶流也不香了?

    雅柏威士忌多款单品价格大跌,泥煤顶流也不香了?

    来源 | 烈酒商业观察编 | 肖海林今年以来,威士忌市场开始出现了降温迹象,越来越多不断暴涨的网红威士忌也开始悄然回归市场理性。近日,LVMH集团旗下苏格兰威士忌品牌雅柏(Ardbeg
  • 2023年,我眼中的字节跳动

    2023年,我眼中的字节跳动

    此时此刻(2023年7月),字节跳动从未上市,也从未公布过任何官方的上市计划;但是这并不妨碍它成为中国最受关注的互联网公司之一。从2016-17年的抖音强势崛起,到2018年的&ldquo;头腾
  • 破圈是B站头上的紧箍咒

    破圈是B站头上的紧箍咒

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之每年的暑期档都少不了瞄准追剧女孩们的古偶剧集,2021年有优酷的《山河令》,2022年有爱奇艺的《苍兰诀》,今年却轮到小破站抓住了追
  • 共享单车的故事讲到哪了?

    共享单车的故事讲到哪了?

    来源丨海克财经与共享充电宝相差不多,共享单车已很久没有被国内热点新闻关照到了。除了一再涨价和用户直呼用不起了。近日多家媒体再发报道称,成都、天津、郑州等地多个共享单
  • 重估百度丨大模型,能撑起百度的“今天”吗?

    重估百度丨大模型,能撑起百度的“今天”吗?

    自象限原创 作者|程心 罗辑2023年之前,对于自己的&ldquo;今天&rdquo;,百度也很迷茫。&ldquo;新业务到 2022 年底还是 0,希望 2023 年出来一个 1。&rdquo;这是2022年底,李彦宏
  • 华为将推出盘古数字人大模型 可帮助用户12小时完成数字人生成

    华为将推出盘古数字人大模型 可帮助用户12小时完成数字人生成

    在今日举行的2023年华为云数字文娱AI创新峰会上,华为云全球Marketing与销售服务总裁石冀琳表示,华为云将在后续推出盘古数字人大模型,可帮助用户12小
  • 机构称Q2全球智能手机出货量同比下滑11% 苹果份额依旧第2

    机构称Q2全球智能手机出货量同比下滑11% 苹果份额依旧第2

    7月20日消息,据外媒报道,研究机构的报告显示,由于需求下滑,今年二季度全球智能手机的出货量,同比下滑了11%,三星、苹果等主要厂商的销量,较去年同期均有下
Top