前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >个人谈谈对ThreadLocal内存泄露的理解

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

作者头像
大忽悠爱学习
发布2023-02-26 17:58:49
6420
发布2023-02-26 17:58:49
举报
文章被收录于专栏:c++与qt学习c++与qt学习

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


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,则会进行一次探测式垃圾回收,回收完后,继续往后遍历

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-02-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 个人谈谈对ThreadLocal内存泄露的理解
  • ThreadLocal作用
  • ThreadLocalMap内存泄露解释
  • 为什么要这样设计
  • ThreadLocalMap的实现思路
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档