【JUC】二十五、ThreadLocal内存泄漏问题(强软弱虚四种引用)

文章目录

  • 1、引用之强软弱虚
  • 2、强引用
  • 3、软引用
  • 4、弱引用
  • 5、虚引用
  • 6、ThreadLocal回顾
  • 7、ThreadLocal使用弱引用的原因
  • 8、清除脏Entry
  • 9、最佳实践

不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露(累积可能导致OOM)。

1、引用之强软弱虚

在这里插入图片描述

  • Reference:强引用
  • SoftReference:软引用
  • WeakReference:弱引用
  • PhantomReference:虚引用

Java 允许使用 finalize方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作(遗言时机)。

//since Java9,已过期
public class MyObject {
    @Override
    protected void finalize() throws Throwable {
        //finalize用于在对象被不可撤销的丢弃之前执行的操作
        System.out.println("----invoke finalize method ~");
    }
}

2、强引用

当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。

强引用是最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象

//eg:
Student student = new Student();
  • 在Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用

  • 当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到,JVM也不会回收

  • 因此强引用是造成Java内存泄漏的主要原因之一

对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)。

public class ReferenceDemo {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        System.out.println("gc before: " + myObject);
        myObject = null;
        //手动触发一次GC
        System.gc();
        System.out.println("gc after: " + myObject);
    }
}

在这里插入图片描述

调用finalize方法是另一线程,这里的打印顺序不用关注。

3、软引用

软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。

总之就是相对强引用而言,稍微松一点,GC触发时:

  • 当系统内存充足时,它不会被回收
  • 当系统内存不足时,它会被回收

软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收。用SoftReference把自定义对象包装一下,对应的引用就变成了软引用。

SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());
System.out.println("gc before: " + softReference.get());
//手动触发一次GC
System.gc();
System.out.println("gc after: " + softReference.get());

在这里插入图片描述

修改Demo类的JVM内存限制,创造一个内存不足的情况:

在这里插入图片描述

创建一个20M的数组,超过了上面的最大内存,模拟内存不足,对象被回收:
在这里插入图片描述

在这里插入图片描述

4、弱引用

对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。

WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject());
System.out.println("gc before: " + weakReference.get());
System.gc();
System.out.println("gc after: " + weakReference.get());

在这里插入图片描述

软引用和弱引用的适用场景举例:

假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取则会严重影响性能,如果一次性全部加载到内存中又可能造成内存溢出,此时使用软引用可以解决这个问题。

设计思路是:

用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。

Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>().

5、虚引用

1)虚引用必须和引用队列ReferenceQueue联合使用

  • 虚引用需要java.lang.ref.PhantomReference类来实现
  • 虚,即形同虚设
  • 虚引用不会决定对象的生命周期
  • 如果一个对象仅持有虚引用,则它和没任何引用一样,随时都可能被垃圾回收器回收
  • 不能单独使用,也不能通过它访问对象
  • 虚引用必须和引用队列ReferenceQueue联合使用,如果虚引用对象被干掉了,就装到队列里

2)PhantomReference虚引用的get方法总是返回null

  • 虚引用的主要作用是跟踪对象被垃圾回收的状态,仅仅是提供了一种确保对象被 finalize以后,做某些事情的通知机制
  • PhantomReference的get方法总是返回null,因此无法访问对应的引用对象

3)处理监控通知使用

  • 设置虚引用关联对象的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理,用来实现比finalize机制更灵活的回收操作

构造方法:

//传入要包装的对象和引用队列
PhantomReference(T referent, ReferenCeQueue<? super T> queue)

继续设置JVM最大内存10M:

public class ReferenceDemo {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
        PhantomReference<MyObject> phantomReference = new PhantomReference<>(myObject, referenceQueue);
        List<byte[]> list = new ArrayList<>();
        new Thread(() -> {
            while (true){
                list.add(new byte[1024 * 1024]); //1M
                //歇500ms,写1M进List
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //验证下每次get都是null
                System.out.println(phantomReference.get() + " list add OK.");
            }
        },"t1").start();

        new Thread(() -> {
            while (true){
                Reference<? extends MyObject> reference = referenceQueue.poll();
                if(reference != null){
                    System.out.println("有虚引用对象被回收,加入了队列");
                    //break;
                }
            }
        },"t2").start();
    }


}

开一个线程去占用内存,另开一个线程去查看队列,可以看到中途虚引用对应的对象被回收时,会加入到队列中。

在这里插入图片描述

在这里插入图片描述

6、ThreadLocal回顾

在这里插入图片描述

ThreadLocal是一个壳子,真正的存储结构是ThreadLocal里的ThreadLocalMap这个内部类,每个Thread对象维护着ThreadLocalMap的引用,ThreadLocalMap则用Entry来进行存储。

  • 调用ThreadLocal的set方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLoca对象,值Value是传递进来的对象
  • 调用ThreadLocal的get方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象

ThreadLocal本身并不存储值(ThreadLocal是一个壳),它只是自己作为一个key来让线程从ThreadLocalMap获取value。正因为这个原理,所以ThreadLocal能够实现线程间的"数据隔离",获取当前线程的局部变量值,不受其他线程影响

7、ThreadLocal使用弱引用的原因

public void function01(){
	//新建一个ThreadLocal对象,t1是强引用指向这个对象
	ThreadLocal<String> t1 = new ThreadLocal<>();
	//实际是创建了一个Entry对象,根据Entry源码知:Entry对象里的key(即ThreadLocal)是弱引用指向这个对象
	//当一个ThreadLocal实例对象只被Entry类实例(或者其它弱引用实例)引用时,它就会被GC回收
	t1.set("code9527");
	t1.get();
}

在这里插入图片描述

当function1方法执行完毕后,栈帧销毁,强引用 t1 也就没有了。但此时线程的ThreadLocalMap里某个entry的key引用还指向这个对象。此时:

  • 若这个key引用是强引用,就会导致key指向的ThreadLocal对象,以及v指向的对象不能被gc回收,造成内存泄漏

  • 若这个key引用是弱引用就大概率会减少内存泄漏的问题(还有一个key为nul的雷,在下面再展开)。

使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收且Entry的key引用指向为null,而此后我们调用get、set、remove方法时,就会尝试删除key为null的entry,可以释放value对象所占用的内存。

8、清除脏Entry

当我们为threadLocal变量赋值,实际上就是当前Entry(threadLocal实例为key,值为value)往这个threadLocalMap中存放。Entry中的key是弱引用,当threadLocal外部强引用被置为null(比如前面例子的t1=null),那么系统 GC 的时候,根据可达性分析,这个threadLocal实例就没有任何一条路能够引用到它, 这个ThreadLocal势必会被回收。

这样一来,ThreadLocalMap中就会出现key为nul的Entry,就没有办法访问这些key为nul的Entry的value,如果当前线程再迟迟不结束的话(线程池,线程在不断复用),这些key为null的Entry的value就会一直存在一条强引用:某个线程池中线程T1的引用Thread Ref ⇒ Thread ⇒ ThreadLocalMap ⇒ Entry ⇒ value ,因此永远无法回收,最后造成内存泄漏。

当然,如果当前thread运行结束,threadLocal、threadLocalMap、Entry没有引用链可达,在垃圾回收的时候都会被系统进行回收。

关于以上key为null的脏Entry的清除 ===> expungeStaleEntry方法

其中,get、set、remove等方法源码中,都有调用expungeStaleEntry方法,如get --> 调getEntry方法 --> getEntryAfterMiss方法:

在这里插入图片描述

虽然弱引用,保证了key指向的ThredLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现key为nul时才会去回收整个entry、value,因此弱引用不能100%保证内存不泄露,我们要在不使用某个ThreadLocal对象后,手动调用remoev方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。

总结:

  • 弱引用保证ThreadLocal对象被及时回收,key为null的Entry会累积
  • get、set时检查所有键为null的Entry对象并删除

9、最佳实践

  • 【建议】创建ThreadLocal对象采用静态方法ThreadLocal.withInitial(() -> 初始值)
  • 【建议】把ThreadLocal修饰为static(若某个属性所有对象都相同,则用静态变量,存方法区,如国籍,这样只在方法区保存一份,可避免不必要的内存空间浪费,反之,则是实例变量)
  • 【强制】用完手动remove

在这里插入图片描述

最后,对ThreadLocal的总结:

  • ThreadLocal 并不解决线程间共享数据的问题
  • ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景
  • ThreadLocal 通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
  • 每个线程持有一个只属于自己的专属Map并维护了ThreadLocal对象与具体实例的映射,该Map由于只被持有它的线程访问,故不存在线程安全以及锁的问题
  • ThreadLocalMap的Entry对ThreadLocal的引用为弱引用,避免了ThreadLocal对象无法被回收的问题
  • 都会通过expungeStaleEntry、cleanSomeSlots、replaceStaleEntry这三个方法回收键为 null 的 Entry对象的值 以及 Entry 对象本身,从而防止内存泄漏,属于安全加固的方法
  • 群雄逐鹿起纷争,人各一份天下安

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Echarts小问题汇总

文章目录 Echarts小问题汇总1.柱状图第一条柱子遮挡Y轴解决方法2.在大屏渲染后 拖到小屏变模糊3.相邻柱状图中间不要有空隙4.实现echarts图表自适应5.单个柱状图最大宽度 Echarts小问题汇总 记录工作中使用Echarts的遇见的一些小问题&#xff0c;后续会不断进行补充 1.柱状图…

三数之和(LeetCode 15)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路方法一&#xff1a;暴力法方法二&#xff1a;排序双指针 5.实现示例参考文献 1.问题描述 给你一个整数数组 nums&#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时…

P1单片机定时器配置及定时器中断——C51(超详细)

目录 1. 简介 1.1 概念解读 1.2 定时器怎么定时 1.什么是晶振 2.什么是时钟周期 3.什么是机器周期 4.加1经过了多少时间 1.3 定时器编程 1.如何算出10ms定时器的初值(TL0 TH0) 2.关于TCON ,怎么知道爆表 3.怎么开始计时(TR0) 4.定时器使用是有很多种模式的&#xf…

Gerrit的使用

项目存储配置 为了能够模拟开发人员和审核人员两个角色&#xff0c;需要有1台服务器模拟操作提交和审核 登陆linux服务器账户&#xff0c;并生成id_rsa.pub 添加git配置 Git配置一般存储的是name 和 email地址 这里的email地址需要和gerrit系统的账号的email地址一致&#…

佛山陶企再造行业新风口,开启中国陶瓷下半场

近年来&#xff0c;消费形态逐渐呈现年轻化、时尚化、数字化的趋势&#xff0c;新一地居住者对于居住环境的品质和舒适度要求日益提高。伴随着新消费势力的崛起&#xff0c;家居建材行业消费转型升级已成必然。“千年陶都”佛山作为我国陶瓷行业的风向标&#xff0c;率先推进技…

SD-WAN组网案例分享——简单高效的远程视频监控方案

在网络化和信息化建设的推动下&#xff0c;远程视频监控设备的应用范围已经不再局限于政府部门和金融行业。中小企业对远程视频监控设备的需求也在持续增长。 案例背景 本次案例分享的是一家大型制造业企业&#xff0c;该企业拥有遍布全国各地的生产厂房和仓库。然而&#xff…

GPS定位与IP地址定位的差异及应用场景

随着科技的不断发展&#xff0c;定位技术在日常生活和商业应用中变得越来越普遍。在定位技术中&#xff0c;GPS&#xff08;全球定位系统&#xff09;和IP地址定位是两种常见的方法。本文将探讨GPS定位与IP地址定位的差异以及它们在不同应用场景中的应用。 1. GPS定位 a. 工作…

flink-1.17.2的单节点部署

flink 简介 Apache Flink 是一个开源的流处理和批处理框架&#xff0c;用于大数据处理和分析。它旨在以实时和批处理模式高效处理大量数据。Flink 支持事件时间处理、精确一次语义、有状态计算等关键功能。 以下是与Apache Flink相关的一些主要特性和概念&#xff1a; 流处理…

故障注入测试有哪些多重作用?

在软件开发的世界中&#xff0c;保证系统的鲁棒性和稳定性至关重要。为了应对各种潜在的故障和异常情况&#xff0c;测试团队采用了各种测试方法&#xff0c;其中之一就是故障注入测试。这种测试方法的目标是有目的地向系统引入故障&#xff0c;以评估系统在面对异常情况时的表…

响应式编程一之基础夯实(初学必看!)

响应式编程一之基础夯实&#xff08;初学必看&#xff01;&#xff09; 函数式编程常见lambda表达式求一个数组里面的最小值代码简洁的函数式编程返回指定对象的接口实例JDK8 新特性jdk8函数式接口predicate 判断hashmap是否为空consumer总结方法引用示例lambda表达式的类型推断…

解题方式篇-回溯

回溯算法 1、简介 简介&#xff1a;回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。 回溯是递归的副产品&#xff0c;只要有递归就会有回溯。回溯是一种暴力的搜索方式。 回溯法&#xff0c;一般可以解决如下几种问题&#xff1a;组合&#xff08;无序&#xff0…

西南科技大学数字电子技术实验五(用计数器设计简单秒表)预习报告

一、计算/设计过程 说明&#xff1a;本实验是验证性实验&#xff0c;计算预测验证结果。是设计性实验一定要从系统指标计算出元件参数过程&#xff0c;越详细越好。用公式输入法完成相关公式内容&#xff0c;不得贴手写图片。&#xff08;注意&#xff1a;从抽象公式直接得出结…

Keil 编译输出信息分析:Program size: Code, RO-data , RW-data, ZI-data

一般 MCU 包含的存储空间有&#xff1a;片内 Flash 与片内 RAM&#xff0c;RAM 相当于内存&#xff0c;Flash 相当于硬盘。编译器会将一个程序分类为好几个部分&#xff0c;分别存储在 MCU 不同的存储区。 如图所示&#xff0c;在Keil中编译工程成功后&#xff0c;在下面的Bul…

AI+无代码助力企业供应链优化

内容来自演讲&#xff1a;潘峰 | 预见明日科技&#xff08;北京&#xff09;有限公司 | CEO 摘要 本文介绍了企业供应链中的挑战和解决方案。文章指出&#xff0c;供应链成本占企业经营成本的大部分&#xff0c;且存在供给端和需求端的高度不确定性。为应对这种不确定性&…

Embedding压缩之基于二进制码的Hash Embedding

推荐系统中&#xff0c;ID类特征的表示学习&#xff08;embedding learning&#xff09;是深度学习模型成功的关键&#xff0c;因为这些embedding参数占据模型的大部分体积。这些模型标准的做法是为每一个ID特征分配一个unique embedding vectors&#xff0c;但这也导致存储emb…

【QT 5 调试软件+(Linux下验证>>>>串口相关初试串口)+Windows下qt代码在Linux下运行+参考win下历程+基础样例】

【QT 5 调试软件Linux下验证>>>>串口相关初试串口参考win下历程基础样例】 1、前言2、实验环境3、先行了解4、自我总结-win下工程切到Linux下1、平台无关的代码&#xff1a;2、依赖的库&#xff1a;3、文件路径和换行符&#xff1a;4、编译器差异&#xff1a;5、构…

揭秘高效大型语言模型:技术、方法与应用展望

近年来&#xff0c;大型语言模型&#xff08;LLMs&#xff09;在自然语言处理领域取得了显著的进展&#xff0c;如GPT-series(GPT-3, GPT-4)、Google-series(Gemini, PaLM), Meta-series(LLAMA1&2), BLOOM, GLM等模型在各种任务中展现出惊人的能力。然而&#xff0c;随着模…

IDC报告:国内游戏云市场,腾讯云用量规模位列第一

12月12日消息&#xff0c;IDC公布最新的《中国游戏云市场跟踪研究&#xff0c;2022H2》报告&#xff08;以下简称“《报告》”&#xff09;显示&#xff0c;腾讯云凭借全球化节点布局以及国际领先的游戏技术积累&#xff0c;在整体规模、云游戏流路数、CDN流量峰值带宽等多维度…

C++笔记之Delegate和委托构造(Delegating constructor)

C笔记之Delegate和委托构造辨析 code review! —— 杭州 2023-12-10 参考博文&#xff1a;C笔记之文档术语——将可调用对象作为函数参数 文章目录 C笔记之Delegate和委托构造辨析0.有道词典&#xff1a;英语发音1.ChatGPT&#xff1a;delegate概念详解2.Delegate和“将可调…

Python异常、模块和包

Python异常、模块和包 1.了解异常2.异常的捕获方法3.异常的传递4.Python模块5.Python包 1.了解异常 1.1什么是异常 当检测到一个错误是&#xff0c;Python解释器就无法继续执行了&#xff0c;发而出现了一些错误提示&#xff0c;这就是所谓的“异常”&#xff0c;也就是我们常…