IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 深耕Java多线程 - ThreadLocal造成内存泄漏的原因和解决方案 -> 正文阅读

[Java知识库]深耕Java多线程 - ThreadLocal造成内存泄漏的原因和解决方案


问题:

1、ThreadLocal造成内存泄漏的原因?
2、ThreadLocal内存泄漏解决方案?

1. ThreadLocal造成内存泄漏的原因?

ThreadLocal的操作都是基于ThreadLocalMap展开的,而ThreadLocalMap是ThreadLocal的一个静态内部类,其实现了一套简单的Map结构。Entry用于保存ThreadLocalMap的Key-Value对条目,但是Entry使用了对ThreadLocal实例进行包装之后的弱引用(WeakReference)作为Key,其代码如下:

static class ThreadLocalMap {
    // map的条目数,作为哈希表使用
    private Entry[] table;
    
    // Entry继承了WeakReference,并使用WeakReference对key进行了包装
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
	}
}

为什么Entry需要使用弱引用对Key进行包装,而不是直接使用ThreadLocal实例作为Key呢?

这里从一个简单的例子入手,假设有一个方法funcA()创建了一个“线程本地变量”,具体如下:

public void funA(){
    // 创建一个线程本地变量
    ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
    threadLocal.set(100);
    threadLocal.get();
    // 函数末尾
}

当线程tn执行funcA()方法到其末尾时,线程tn相关的JVM栈内存以及内部ThreadLocalMap成员的结构大致如图:
在这里插入图片描述
当线程tn执行完funcA()方法后,funcA()的方法栈帧将被销毁,强引用local的值也就没有了,但此时线程的ThreadLocalMap中对应的Entry的Key引用还指向ThreadLocal实例。如果Entry的Key引用是强引用,就会导致Key引用指向的ThreadLocal实例及其Value值都不能被GC回收,这将造成严重的内存泄漏问题。
在这里插入图片描述
什么是弱引用呢?

仅有弱引用(Weak Reference)指向的对象只能生存到下一次垃圾回收之前。换句话说,当GC发生时,无论内存够不够,仅有弱引用所指向的对象都会被回收。而拥有强引用指向的对象则不会被直接回收。

什么是内存泄漏?

不再用到的内存没有及时释放(归还给系统),就叫作内存泄漏。对于持续运行的服务进程必须及时释放内存,否则内存占用率越来越高,轻则影响系统性能,重则导致进程崩溃甚至系统崩溃。

由于ThreadLocalMap中Entry的Key使用了弱引用,在下次GC发生时,就可以使那些没有被其他强引用指向、仅被Entry的Key所指向的ThreadLocal实例能被顺利回收。并且,在Entry的Key引用被回收之后,其Entry的Key值变为null。后续当ThreadLocal的get()、set()或remove()被调用时,ThreadLocalMap的内部代码会清除这些Key为null的Entry,从而完成相应的内存释放。

总结一下,使用ThreadLocal会发生内存泄漏的前提条件如下:

(1)线程长时间运行而没有被销毁。线程池中的Thread实例很容易满足此条件。

(2)ThreadLocal引用被设置为null,且后续在同一Thread实例执行期间,没有发生对其他ThreadLocal实例的get()、set()或remove()操作。只要存在一个针对任何ThreadLocal实例的get()、set()或remove()操作,就会触发Thread实例拥有的ThreadLocalMap的Key为null的Entry清理工作,释放掉ThreadLocal弱引用为null的Entry。

综合以上两点可以看出:使用ThreadLocal出现内存泄漏还是比较容易的。但是一般公司对如何使用ThreadLocal都有编程规范,只要大家按照规范编写程序,也没有那么容易发生内存泄漏。

2. ThreadLocal内存泄漏解决方案?

编程规范推荐使用static final修饰ThreadLocal对象。

ThreadLocal实例作为ThreadLocalMap的Key,针对一个线程内的所有操作是共享的,所以建议设置static修饰符,以便被所有的对象共享。由于静态变量会在类第一次被使用时装载,只会分配一次存储空间,此类的所有实例都会共享这个存储空间,所以使用static修饰ThreadLocal就会节约内存空间。另外,为了确保ThreadLocal实例的唯一性,除了使用static修饰之外,还会使用final进行加强修饰,以防止其在使用过程中发生动态变更。

@Data
public class Person {
    private int age = 10;
}
public class Test {
    private static final ThreadLocal<Person> threadLocal = new ThreadLocal<>();
}

凡事都有两面性,使用static、final修饰ThreadLocal实例也会带来副作用,使得Thread实例内部的ThreadLocalMap中Entry的Key在Thread实例的生命期内将始终保持为非null,从而导致Key所在的Entry不会被自动清空,这就会让Entry中的Value指向的对象(person)一直存在强引用,于是Value指向的对象(person)在线程生命期内不会被释放,最终导致内存泄漏。所以,在使用完static、final修饰的ThreadLocal实例之后,必须调用remove()来进行显式的释放操作。

总结:

由于ThreadLocal使用不当会导致严重的内存泄漏问题,所以为了更好地避免内存泄漏问题的发生,我们使用ThreadLocal时遵守以下两个原则:

(1)尽量使用private static final修饰ThreadLocal实例。使用private与final修饰符主要是为了尽可能不让他人修改、变更ThreadLocal变量的引用,使用static修饰符主要是为了确保ThreadLocal实例的全局唯一。

(2)ThreadLocal使用完成之后务必调用remove()方法。这是简单、有效地避免ThreadLocal引发内存泄漏问题的方法。

总之,使用ThreadLocal能实现每个线程都有一份变量的本地值,其原因是每个线程都有自己独立的ThreadLocalMap空间,本质上属于以空间换时间的设计思路,该设计思路属于另一种意义的“无锁编程”。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-11 21:59:56  更:2022-03-11 22:02:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:55:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码