全面分析!ThreadLocal内存泄漏的原因!

简介: 全面分析!ThreadLocal内存泄漏的原因!

前言

ThreadLocal是一个大家都不陌生的对象,他的作用是能够进行跨方法的值传递,他的数据保存在线程中,每一个线程有一份单独的数据,我们可以用来来记录方法的执行时长等功能,但是使用他也会造成风险,比如会造成内存溢出,那么是什么情况下回有内存溢出的情况呢,下面我们就聊一聊

内存溢出原因

可能大部分的人都会说,是因为弱引用,因为弱引用时,gc会直接内存回收掉,那么引用的值就会为null,这一点其实我一直很困惑,如果弱引用直接内存回收掉,那么存在threadLocal里面的值不就没有了吗?那ThreadLocal使用起来风险不就会非常大,进行内存回收时数据不就丢失了吗?这是很严重的事故呀!!!

那为什么还在用呢?
一图胜前言

image.png
从图上可以看出来,其实ThreadLocal关联两个引用,在ThreadLcocal对象上是强引用,而在Entry对象上是弱引用,那么如果gc想回收,必须ThreadLocal对象本身不存在强引用,那什么时候ThreadLocal不存在强引用时,第一种Thead对象结束,我们知道Thread对象其实就是线程对象,他结束就意味着线程结束,线程结束内存被回收时没问题的。

但是还有一种,就是会产生内存泄漏的原因,就是在线程没结束时将ThreadLocal对象赋值为null,这意味着对象已经死亡,可以进行垃圾回收,这时ThreadLocal就不再存在强引用,而Entry对象引用的ThreadLocal由于是弱引用,也会被垃圾回收掉,而Value对象还存在Entry的强引用,value对象不会被垃圾回收,这就导致只要线程不结束,那Entry的key就为null,对应的value也就无法被访问,造成了内存泄漏,只有等线程结束才会被回收,因为在线程结束后,value才不会存在强引用,这里也解决了上面我提出的疑惑,数据丢失的问题,基于上面的分析,只要线程不结束,数据是不会丢失的。

除此之外,如果产生了内存泄漏,其实线程的存活时间比较短的话,影响程度一般不会很大,但是如果是在线程池中使用,就会产生较大的影响,因为线程池中的线程由于要重复利用,核心线程不会进入终止状态,这就意味着,线程会持续存货不会消亡,导致一直不被回收

ThreadLocal也提供了remove方法来方式来防止内存泄漏。我们看一下remove方法的实现


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

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;
        }
    }
}

首先从想成中获取当前线程对应的ThreadLocalMap,然后判断如果不为空,就将Entry对象中的内存都移除掉,这里面就包含value对象,也就避免了内存泄漏。

ThreadLocal设置弱引用原因

从上面的分析,我们发现内存泄漏的一部分原因是由于,Entry对象的弱引用,但是为什么设置Entry引用的ThreadLocal对象为弱引用呢,其实将他设置成弱引用也是防止内存泄漏的,看到这里,一些读者可定会很困惑,内存泄漏的原因和他有关,但是为什么他却又是防止内存泄漏呢?

其实,ThreadLocal的内存泄漏可能分两种

  1. key内存泄漏
    这种内存泄漏我们是看不见的,为什么,就是因为将ThreadLocal使用弱引用才解决了这个问题,如果设计成强引用,这时我们在业务方法中将ThreadLocal设置为null,这时Entry对象中对ThreadLocal是强引用,就意味着ThreadLocal不是进行垃圾回收,直接导致了key的内存泄漏。
  2. value内存泄漏
    其实value的内存泄漏就是我们前面分析的内存泄漏,为了防止他发生,所以调用remove方法,除此之外,set和get方法也有将ThreadLocal为null的Enrty删除掉的逻辑,目的也就是处理这种情况

后记

ThreadLocal对象的内存溢出估计很多人都了解其原因,但是只有了解其存储结构才能更深刻的了解到他的泄漏原因,这个问题其实困惑了我很久,深挖之后还是有一定的收获的。

相关文章
|
Web App开发 监控 JavaScript
监控和分析 JavaScript 内存使用情况
【10月更文挑战第30天】通过使用上述的浏览器开发者工具、性能分析工具和内存泄漏检测工具,可以有效地监控和分析JavaScript内存使用情况,及时发现和解决内存泄漏、过度内存消耗等问题,从而提高JavaScript应用程序的性能和稳定性。在实际开发中,可以根据具体的需求和场景选择合适的工具和方法来进行内存监控和分析。
|
11月前
|
Java
我们来说一说 ThreadLocal 内存泄漏
我是小假 期待与你的下一次相遇 ~
657 5
|
存储 弹性计算 缓存
阿里云服务器ECS经济型、通用算力、计算型、通用和内存型选购指南及使用场景分析
本文详细解析阿里云ECS服务器的经济型、通用算力型、计算型、通用型和内存型实例的区别及适用场景,涵盖性能特点、配置比例与实际应用,助你根据业务需求精准选型,提升资源利用率并降低成本。
826 3
|
10月前
|
设计模式 缓存 Java
【JUC】(4)从JMM内存模型的角度来分析CAS并发性问题
本篇文章将从JMM内存模型的角度来分析CAS并发性问题; 内容包含:介绍JMM、CAS、balking犹豫模式、二次检查锁、指令重排问题
258 1
|
存储 人工智能 自然语言处理
AI代理内存消耗过大?9种优化策略对比分析
在AI代理系统中,多代理协作虽能提升整体准确性,但真正决定性能的关键因素之一是**内存管理**。随着对话深度和长度的增加,内存消耗呈指数级增长,主要源于历史上下文、工具调用记录、数据库查询结果等组件的持续积累。本文深入探讨了从基础到高级的九种内存优化技术,涵盖顺序存储、滑动窗口、摘要型内存、基于检索的系统、内存增强变换器、分层优化、图形化记忆网络、压缩整合策略以及类操作系统内存管理。通过统一框架下的代码实现与性能评估,分析了每种技术的适用场景与局限性,为构建高效、可扩展的AI代理系统提供了系统性的优化路径和技术参考。
968 4
AI代理内存消耗过大?9种优化策略对比分析
|
JavaScript
如何使用内存快照分析工具来分析Node.js应用的内存问题?
需要注意的是,不同的内存快照分析工具可能具有不同的功能和操作方式,在使用时需要根据具体工具的说明和特点进行灵活运用。
775 159
|
存储 Java
课时4:对象内存分析
接下来对对象实例化操作展开初步分析。在整个课程学习中,对象使用环节往往是最棘手的问题所在。
188 4
|
Java 编译器 Go
go的内存逃逸分析
内存逃逸分析是Go编译器在编译期间根据变量的类型和作用域,确定变量分配在堆上还是栈上的过程。如果变量需要分配在堆上,则称作内存逃逸。Go语言有自动内存管理(GC),开发者无需手动释放内存,但编译器需准确分配内存以优化性能。常见的内存逃逸场景包括返回局部变量的指针、使用`interface{}`动态类型、栈空间不足和闭包等。内存逃逸会影响性能,因为操作堆比栈慢,且增加GC压力。合理使用内存逃逸分析工具(如`-gcflags=-m`)有助于编写高效代码。
336 2
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
752 1
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
1978 9