Java ThreadLocal 与 OOM

ThreadLocal 实例通常都是 static 类型,用于关联线程和线程上下文。

ThreadLocal 提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。

总结:

  • 线程并发: 在多线程并发的场景下
  • 传递数据: 我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
  • 线程隔离: 每个线程的变量都是独立的,不会互相影响

一、ThreadLocal 的内部结构

JDK 早期 ThreadLocal 设计:

  • 每个 ThreadLocal 都创建一个 Map
  • 线程作为 Map 的 key,要存储的局部变量作为 Map 的 value

这样就能达到各个线程的局部变量隔离的效果。

Java ThreadLocal 与 OOM

在 JDK8 中 ThreadLocal 的设计:

  • 每个 Thread 线程内部都有一个 Map (ThreadLocalMap)
  • Map 里面存储 ThreadLocal 对象(key)和线程的变量副本(value)
  • Thread 内部的 Map 是由 ThreadLocal 维护的,由 ThreadLocal 负责向 map 获取和设置线程的变量值。

对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

Java ThreadLocal 与 OOM

这样设计的好处有如下两个优势:

  • 每个 Map 存储的 Entry 数量会变少。因为之前的存储数量由 Thread 的数量决定,现在是由 ThreadLocal 的数量决定。在实际运用当中,往往 ThreadLocal 的数量要少于 Thread 的数量。
  • 当 Thread 销毁之后,对应的 ThreadLocalMap 也会随之销毁,减少内存的使用。

二、OOM

public class ThreadLocal {
    static class ThreadLocalMap {
        private Entry[] table;
        static class Entry extends WeakReference> { // 弱引用

Java 中的引用有 4 种类型: 强、软、弱、虚。当前这个问题主要涉及到强引用和弱引用:

  • 强引用(StrongReference):最常见的普通对象引用,只要还有强引用指向对象,就表明对象还”活着”,垃圾回收器就不会回收这种对象。
  • 弱引用(WeakReference):垃圾回收器一旦发现了只有弱引用的对象,不管当前内存是否充足,都会回收。

如果 key 使用强引用

Java ThreadLocal 与 OOM
  1. 假设在业务代码中使用完 ThreadLocal ,ThreadLocal Ref 被回收了。
  2. 但是因为 ThreadLocalMap 的 Entry 强引用了 ThreadLocal,造成 ThreadLocal 无法被回收。
  3. 在没有手动删除这个 Entry 以及 CurrentThread 依然运行的前提下,始终有强引用链 threadRef -> currentThread -> threadLocalMap -> entry,Entry 就不会被回收(Entry 中包括了 ThreadLocal 实例和 value),导致 Entry 内存泄漏。

也就是说,ThreadLocalMap 中的 key 使用了强引用,是无法完全避免内存泄漏的。

如果 key 使用弱引用

Java ThreadLocal 与 OOM
  1. 同样假设在业务代码中使用完 ThreadLocal,ThreadLocal Ref 被回收了。
  2. 由于 ThreadLocalMap 只持有 ThreadLocal 的弱引用,没有任何强引用指向 ThreadLocal 实例,所以 ThreadLocal 就可以顺利被 gc 回收,此时 Entry 中的 key=null。
  3. 但是在没有手动删除这个 Entry 以及 CurrentThread 依然运行的前提下,也存在有强引用链 threadRef -> currentThread -> threadLocalMap -> entry -> value,value 不会被回收,而这块 value 永远不会被访问到了,导致 value 内存泄漏。

也就是说,ThreadLocalMap 中的 key 使用了弱引用,也有可能内存泄漏。

出现内存泄漏的真实原因

比较以上两种情况,可以发现内存泄漏的发生跟 ThreadLocalMap 中的 key 是否使用弱引用是没有关系的。在以上两种内存泄漏的情况中,都有两个前提:

  1. 没有手动删除这个 Entry
  2. CurrentThread 依然运行

第一点,只要在使用完 ThreadLocal,调用其 remove 方法删除对应的 Entry,就能避免内存泄漏。

第二点,由于 ThreadLocalMap 是 Thread 的一个属性,被当前线程所引用,所以它的生命周期跟 Thread 一样长。那么在使用完 ThreadLocal 之后,如果当前 Thread 也随之执行结束,ThreadLocalMap 自然也会被 gc 回收,从根源上避免了内存泄漏。

综上,ThreadLocal 内存泄漏的根源是:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏。

为什么使用弱引用

无论 ThreadLocalMap 中的 key 使用哪种类型引用都无法完全避免内存泄漏,跟使用弱引用没有关系。

要避免内存泄漏有两种方式:

  • 使用完 ThreadLocal,调用其 remove 方法删除对应的 Entry
  • 使用完 ThreadLocal,当前 Thread 也随之运行结束

相对第一种方式,第二种方式显然更不好控制,特别是使用线程池的时候,线程结束是不会销毁的。

也就是说,只要记得在使用完 ThreadLocal 及时的调用 remove,无论 key 是强引用还是弱引用都不会有问题。那么为什么 key 要用弱引用呢?

事实上,在 ThreadLocalMap 中的 set/getEntry 方法中,会对 key 为 null(也即是 ThreadLocal 为 null)进行判断,如果为 null 的话,会把 value 也设置为 null 的。

这就意味着使用完 ThreadLocal,CurrentThread 依然运行的前提下,就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收,对应的 value 在下一次 ThreadLocalMap 调用 set、get、remove 中的任一方法的时候会被清除,从而避免内存泄漏。

Hash 冲突

解决办法和 HashMap 不一样,ThreadLocalMap 使用的是线性探测法(开放定址法),HashMap 使用的是拉链法(链地址法)。

该方法一次探测下一个地址,直到有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出。

举个例子:假设当前 table 长度为 16,也就是说如果计算出来 key 的 hash 值为 14,如果 table[14] 上已经有值,并且其 key 与当前 key 不一致,那么就发生了 hash 冲突,这个时候将 14 加 1 得到 15,取 table[15] 进行判断,这个时候如果还是冲突会回到 0,取 table[0],以此类推,直到可以插入。可以把 Entry[] table 看成一个环形数组。

https://www.bilibili.com/video/BV1N741127FH

https://blog.csdn.net/f641385712/article/details/104583169

https://blog.csdn.net/f641385712/article/details/104573489

Original: https://www.cnblogs.com/jhxxb/p/14489301.html
Author: 江湖小小白
Title: Java ThreadLocal 与 OOM

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/537411/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 如何引用 System.Runtime.Serialization.Json;

    今天新开的一个项目突然发现引用System.Runtime.Serialization.Json 提示 命名空间 不存在类型或命名空间名称 json 明明前段时间刚开发的WCF是很…

    Java 2023年6月14日
    079
  • java中的日志配置

    slf4j是java中常用的日志框架,有许多具体实现,比如slf4j-simple等。 一、maven配置 org.slf4j   slf4j-api   1.7.26   com…

    Java 2023年5月29日
    071
  • Java多线程基础入门

    任务? 程序? 进程 Process?执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位 线程 Thread?一个进程中可以包含若干个线程,进程中至少有一个线程。线程是…

    Java 2023年6月9日
    076
  • spring 自定义实体类读取配置文件

    1.spring项目中有application.properties.配置信息如下: #FTP配置信息——————–#FTP服务器hostnameftp….

    Java 2023年5月30日
    082
  • 明明准备的挺好,面试又挂了……

    面试准备的时候,你是否总觉得花费的时间过长?又或者有些面试题你明明了解过,但是面试的时候,给出的答案总是不那么令人满意。甚至,每次刷完面试题,你觉得答得很好,但是总也没得到 Off…

    Java 2023年6月7日
    077
  • WebSocket 服务端未启动时,客户端重连报错

    当WebSocket服务端未启动时,我们在客户端申请连接,会报 System.Net.Sockets.SocketException 异常。 当然,我们调试时异常设置默认是不勾选这…

    Java 2023年5月30日
    068
  • 关系数据库元数据处理类(二) 定义查询元数据接口

    1 /// 2 /// 数据库元数据处理 3 /// 4 public interface IMetadata 5 { 6 #region DataBase 7 /// 8 ///…

    Java 2023年6月5日
    079
  • 三十二张图告诉你,Jenkins构建Spring Boot 有多简单~

    持续原创输出,点击上方蓝字关注我 目录 前言 如何安装Jenkins? 环境准备 开始安装Jenkins 初始化配置 访问首页 输入管理员密码 安装插件 创建管理员 实例配置 配置…

    Java 2023年6月14日
    091
  • 在Java中入门,读取和创建Excel,Apache POI的使用

    POI介绍 Apache POI 简介是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office(Exc…

    Java 2023年5月29日
    075
  • java 浅拷贝和深拷贝

    java 浅拷贝和深拷贝 java 浅拷贝和深拷贝 简单理解: 拷贝基本数据类型 拷贝引用类型 浅拷贝: 深拷贝: 不可变类的特殊性: 简单理解: 浅拷贝:拷贝地址。原变量改变,新…

    Java 2023年6月16日
    090
  • Docker安装Jenkins打包Maven项目为Docker镜像并运行【保姆级图文教学】

    一、前言 Jenkins作为CI、CD的先驱者,虽然现在的风头没有Gitlab强了,但是还是老当益壮,很多中小公司还是使用比较广泛的。最近小编经历了一次Jenkins发包,感觉还不…

    Java 2023年6月15日
    094
  • HTTP长连接–Keep-Alive

    一、 HTTP/1.0 HTTP1.0版本的Keep-alive并不像HTTP1.1那样是默认发送的,所以要想连接得到保持,必须手动配置发送connection:keep-aliv…

    Java 2023年6月13日
    069
  • MySQL版本引起的错误

    接上一篇帖子,博主在CentOS上安装了最新版的MySQL容器(版本为8.0.19),在使用本地springBoot项目连接,启动项目后操作登录系统时报错。 请看代码: com.m…

    Java 2023年6月7日
    0126
  • java java19 协程 虚拟线程 virtual threads 尝鲜

    jdk下载 java19 九月份就GA了,大家快试试协程吧。。 jdk19我放在群文件了 Q群 4915800自行下载也可以 https://openjdk.org/project…

    Java 2023年5月29日
    082
  • 链表相加_2_445

    给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。你可以…

    Java 2023年6月5日
    082
  • 【转】【数学】矩阵求逆的几何意义

    向量:[a1, a2, a3, …, an]矩阵:a11, a12, a13, …, a1na21, a22, a23, …, a2n&#823…

    Java 2023年5月29日
    0195
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球