个人谈谈对ThreadLocal内存泄露的理解

news2024/12/26 12:44:46

个人谈谈对ThreadLocal内存泄露的理解

  • ThreadLocal作用
  • ThreadLocalMap内存泄露解释
  • 为什么要这样设计
  • ThreadLocalMap的实现思路


ThreadLocal作用

平时我们会使用ThreadLocal来存放当前线程的副本数据,让当前线程执行流中各个位置,都可以从ThreadLocal中获取到想要的线程副本数据,而无需通过方法参数逐级传递,减少了代码的耦合。

那么我们通过ThreadLocal设置的线程副本数据具体是保存在哪里的呢? 怎么保存的呢?

这里简单说一下: 我们其实是通过ThreadLocal对象间接操作Thread对象内部的ThreadLocalMap线程副本数据存储源的

在这里插入图片描述
首先,基于OOP思想,Thread类应该聚合了当前线程相关信息,如: 线程ID,线程名,线程副本数据存储源等 。

为什么不直接通过Thread对象暴露出接口来访问内部的ThreadLocalMap,而采用ThreadLocal进行间接访问,这其实是遵循了"最小知道原则",即: 如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

因为我们只是想设置和保存数据到当前线程的存储源中,而不想知道线程对象其他细节,因此采用ThreadLocal实现这一特定功能。

扩展一点: 之所以ThreadLocal对象单独设计成一个类,而不是以静态内部类的形式出现在Thread类中,是因为这遵循了"单一职责原则",线程副本数据并不是线程对象必须具备的属性,类设计的时候只保留本身必须的属性即可。


ThreadLocalMap内存泄露解释

ThreadLocalMap本身是由一组Entry组成的,每个Entry具体又包含了key和value两部分,key的类型是ThreadLocal,val就是我们设置到线程的副本数据。

此处Entry的key采用的是弱引用实现的:
在这里插入图片描述
实际我们传入的ThreadLocal对象是被WeakReference弱引用类中的referent属性指向的,表示当前ThreadLocal被一个弱引用对象指向着:

在这里插入图片描述
内存泄露发生场景:
在这里插入图片描述
由于key为null,value依然占据内存空间,但是无法被访问到,所以就称这种情况下产生了内存泄露。


为什么要这样设计

为什么要把ThreadLocalMap中的Entry设置成弱引用对象呢?如果设置成普通的map集合会怎么样呢?

首先,我们采用普通的map集合作为线程副本数据存储实现,那么当前我们的应用程序失去了对ThreadLocal对象的强引用时,我们就再也无法通过ThreadLocal去访问ThreadLocalMap中我们存储的线程副本数据了,那么此时就可以认为这样一对key:value键值对是垃圾,需要被回收掉。

对于普通的map实现而言,我们无法区分到底哪些ThreadLocal对象确定是应用程序不再访问的,可以被回收掉的,因此也就无法回收这些垃圾键值对占据的空间了,反而会导致某种意义上的内存泄露。

关键问题就是如何知道哪些ThreadLocal对象不会再被应用程序访问,也就是说哪些ThreadLocal对象不再被应用程序中某些变量强引用指向,这个解决办法就是将map中的key设置为弱引用类型。

当我们将map中的key设置为弱引用类型时,当应用程序不再通过强引用指向某个ThreadLocal对象时,我们便可以通过垃圾回收器感知到这一情况,因为垃圾回收器会在垃圾回收时,回收掉这些只被弱引用对象指向的ThreadLocal对象,回收后,对于key就被设置为了NULL,此时Entry不为null。

我们可以对这些key为null的键值对进行清理回收,然后重用这些空间。


ThreadLocalMap的实现思路

此处参考下面这篇文章,来简单聊聊ThreadLocalMap的一个设计思路:

面试官:小伙子,听说你看过ThreadLocal源码?(万字图文深度解析ThreadLocal)

set过程:

  1. 计算ThreadLocal对象的hashcode,然后取余数组大小,得出最终需要放置的数组索引位置
  2. 如果产生hash冲突,采用线性探测法解决,不冲突判断entry是否为null,或者entry的key是否为null ,满足其一,说明该空间可以被使用。不满足,判断key是否相等,相等则进行覆盖操作。
  3. 进入过期key清理过程:
    1. 首先第一步计算得出数组索引位置处开始,向前寻找到过期key首次出现的位置
    2. 从首次出现的位置开始往后执行探测式清理工作,清理过程为:
      1. 遍历到过期entry则设置entry为null
      2. 碰到未过期的entry,通过rehash进行位置重定位,如果定位的位置已经有了数据,则会将未过期的数据放到最靠近此位置的entry为null的桶中,使得rehash后的entry数据距离正确的位置更近一些,减少get时的遍历损耗。
      3. 当遇到entry为null的情况时,结束探测式清理工作。

get过程:

  1. 计算ThreadLocal对象的hashcode,然后取余数组大小,得出数据存放在数组索引位置
  2. 该位置的entry的key与查找的key一致,直接返回
  3. 不一致则采用线性探测法往后遍历,判断哪一个entry的key和当前要查看的key是一致的
    1.这个探测过程中,如果发现了某个entry.key为null,则会进行一次探测式垃圾回收,回收完后,继续往后遍历

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/356005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java SPI 机制详解

在面向对象的设计原则中,一般推荐模块之间基于接口编程,通常情况下调用方模块是不会感知到被调用方模块的内部具体实现。一旦代码里面涉及具体实现类,就违反了开闭原则。如果需要替换一种实现,就需要修改代码。 为了实现在模块装…

使用packetbeat对MySQL进行网络抓包

文章目录一、Packetbeat 简介二、packetbeat部署和使用2.1 官方下载解压2.2 修改配置文件2.3 导入索引模板和dashboard2.4 启动packetbeat三、效果展示一、Packetbeat 简介 Packetbeat 是一款轻量型实时网络数据包分析器,能够将主机和容器中的数据发送至 Logstash 或…

uboot编译分析

uboot编译分析 V 1 –> Q ,在一行命令前面加上表示不会在终端输出命令 KCONFIG_CONFIG ? .config.config 默认是没有的,默认是需要使用命令“make xxx_defconofig”先对uboot进行配置,配置完成就会在uboot根目录下生成.config。如果后续自行调整…

多种方法解决谷歌(chrome)、edge、火狐等浏览器F12打不开调试页面或调试模式(面板)的问题。

文章目录1. 文章引言2. 解决问题3. 解决该问题的其他方法1. 文章引言 不论是前端开发者,还是后端开发者,我们在调试web项目时,偶尔弹出相关错误。 此时,我们需要打开浏览器的调试模式,如下图所示: 通过浏…

智能拣配单解决方案

电子货架标签系统(ESLs),是一种放置在货架上、可替代传统纸质价格标签的电子显示装置, 每一个电子货架标签通过有线或者无线网络与商场计算机数据库相连, 并将最新的商品价格通过电子货架标签上的屏显示出来。 电子…

基于微信小程序图书馆管理系统

开发工具:IDEA、微信小程序服务器:Tomcat9.0, jdk1.8项目构建:maven数据库:mysql5.7前端技术:vue、uniapp服务端技术:springbootmybatis-plus本系统分微信小程序和管理后台两部分,项…

量子计算(7)pyqpanda编程2循环与条件判断

目录 一、QWhile 二、QIf 各位读者老爷们,大家好呀,前些时忙着学校的期末考试,小编好久没更新量子计算的文章啦,这段时间也有读者私信小编,问了一些问题。我知道大家都很急,但大家先别急。这不&#xff0…

【数据结构】——队列

文章目录前言一.什么是队列,队列的特点二、队列相关操作队列的相关操作声明队列的创建1.队列的初始化2.对队列进行销毁3.判断队列是否为空队列4.入队操作5.出队操作6.取出队头数据7. 取出队尾数据8.计算队伍的人数总结前言 本文章讲述的是数据结构的特殊线性表——…

Python3 错误和异常实例及演示

作为 Python 初学者,在刚学习 Python 编程时,经常会看到一些报错信息,在前面我们没有提及,这章节我们会专门介绍。 Python 有2种错误很容易辨认:语法错误和异常。 Python assert(断言)用于判断…

通信算法之一百零四:QPSK完整收发仿真链路

1.发射机物理层基带仿真链路 1.1 % Generates the data to be transmitted [transmittedBin, ~] BitGenerator(); 2.2 % Modulates the bits into QPSK symbols modulatedData QPSKModulator(transmittedBin); 2.3 % Square root Raised Cosine Transmit Filter %comm…

SpringBoot参数请求处理

一、请求映射 请求映射原理 DispatcherServlet 继承了 FrameworkServlet(抽象类,继承了 HttpServletBean,实现了 ApplicationContextAware 接口),重写了 doService() 方法 在 doService() 方法里定义了 doDispatch() 方法;doDi…

概论_第7章_参数估计_真题__求置信区间

真题 2014.10 第30题 测量某物体的质量9次, 测得平均值 x‾15.4\overline x 15.4x15.4 g, 已知测量数据 XXX ~ N(μ,0.09)N(\mu, 0.09)N(μ,0.09) (1) 求该物体质量的置信度为0.95 的置信区间; (2)为了使置信度为0.95 的置信区间…

20 堆排序

文章目录1 堆排序的概念2 堆排序基本思想3 堆排序步骤图解说明4 堆排序的代码实现1 堆排序的概念 1) 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为 O(nlogn)&#xf…

Spring中BeanPostProcessor与循环依赖的问题

Spring中后置处理器与循环依赖的问题 在Spring的bean生命周期中, 我们可以通过实现BeanPostProcessor来对bean初始化前后做操作, 在网上的许多帖子博客中, 不乏可以看见说Spring的AOP是在后置处理器中实现的, 这个理解显然有失偏颇, 很容易误导一般人在后置处理器中对原先的be…

Android 一帧绘制流程分析笔记

和你一起终身学习,这里是程序员Android经典好文推荐,通过阅读本文,您将收获以下知识点:一、显示一帧流程概览二、生产者,消费者 BufferQueue 流转图三、App ,SF Buffer 交互图四、SF 跟 HWC 交互图一、显示一帧流程概览…

16.CSS中使用颜色

使用颜色 在计算机中,传统的模型之一为RGB模型,所有颜色都是通过红色、蓝色、绿色三种颜色进行组合;我们通过数值去表示 例如: 红色(255.0.0)、蓝色(0.0.255)、绿色(0.…

在IDEA中配置jeesite-(文章链接汇总)

建议按照文章顺序操作 原文里可以下载geesite项目 jeesite-github原文链接 jeesite-gitee原文链接–国内推荐 可 直 接 跳 到 此 步 骤 环境准备:JDK 1.8 or 11、17、Maven 3.6、使用 MySQL 5.7 or 8.0 数据库 1-Maven的下载安装配置教程(详细图文&am…

Vue3之循环渲染

1.何为循环渲染 在Vue3中,当我们需要渲染一个数组中的数据到dom元素上时,就需要使用循环渲染。循环渲染可以节约我们大量重复冗余的工作,比如我们去渲染一个下拉菜单的时候,如果不使用循环渲染,那么我们需要手动一项一…

2023美赛C代码思路结果【全部更新完毕】注释详尽

C题已完成全部代码,注释详尽,并增加扰动项,保证大家的结果不会撞 需要全部问题的可以点击:https://www.jdmm.cc/file/2708697/ 下面贴出核心代码: -- coding: utf-8 -- TODO: 入口函数 import numpy as np from…

Mr. Cappuccino的第43杯咖啡——Kubernetes之Pod控制器(二)

Kubernetes之Pod控制器Horizontal Pod Autoscaler(HPA)安装metrics-server创建Pod创建HPA压力测试JobCronJobHorizontal Pod Autoscaler(HPA) 上篇文章中所说的ReplicaSet和Deployment,我们已经可以通过手动执行kubec…