ThreadLocal内存泄漏与解决

目录

什么是Threadlocal?

Threadlocal的基本使用

ThreadLocal的内存泄漏举例

场景1

场景2

场景3

场景4

内存泄漏原因分析

总结


什么是Threadlocal?

   ThreadLocal 是 Java 中的一个类,它提供了线程本地变量的支持。线程本地变量是指被线程拥有并独立于其他线程的变量。每个线程都可以独立地改变自己的副本,而不会影响其他线程的副本。ThreadLocal 主要用于在多线程环境下保持变量的线程封闭性,以实现线程安全。

Threadlocal的基本使用

1. set(T value)

用于设置当前线程的线程本地变量的值。

参数 value 是要设置的值。

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("Hello, ThreadLocal!");

 2. get()

用于获取当前线程的线程本地变量的值。

ThreadLocal<String> threadLocal = new ThreadLocal<>();
String value = threadLocal.get();

3. remove()

用于移除当前线程的线程本地变量。

在一些情况下,手动调用 remove() 可以帮助避免内存泄漏。

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.remove();

 4. initialValue()

该方法是一个 protected 方法,可以被子类重写以提供线程本地变量的初始值。默认情况下,initialValue() 返回 null。

通常情况下,我们会通过 ThreadLocal 的子类并重写 initialValue() 方法来设定线程本地变量的初始值。

public class MyThreadLocal extends ThreadLocal<String> {
    @Override
    protected String initialValue() {
        return "Default Value";
    }
}

// 使用自定义的 MyThreadLocal
MyThreadLocal myThreadLocal = new MyThreadLocal();
String value = myThreadLocal.get();  // 返回 "Default Value"

 上述方法综合使用如下:

public class ThreadLocalExample {

    // 创建一个 ThreadLocal 实例
    private static final ThreadLocal<String> threadLocalValue = new ThreadLocal<>();

    public static void main(String[] args) {
        // 在主线程设置值
        threadLocalValue.set("Main Thread Value");

        // 创建两个子线程
        Thread thread1 = new Thread(() -> {
            // 在子线程1获取值
            String value = threadLocalValue.get();
            System.out.println("Thread 1: " + value); // 输出:Thread 1: null
        });

        Thread thread2 = new Thread(() -> {
            // 在子线程2设置值
            threadLocalValue.set("Thread 2 Value");

            // 在子线程2获取值
            String value = threadLocalValue.get();
            System.out.println("Thread 2: " + value); // 输出:Thread 2: Thread 2 Value
        });

        // 启动子线程
        thread1.start();
        thread2.start();

        // 在主线程获取值
        String mainThreadValue = threadLocalValue.get();
        System.out.println("Main Thread: " + mainThreadValue); // 输出:Main Thread: Main Thread Value

        // 清理主线程的值
        threadLocalValue.remove();
    }
}

       在上述案例中可以看出,每个线程中的Threadlocal变量都是独立的副本。ThreadLocal 提供了一种在多线程环境下安全地存储和访问线程本地变量的机制。每个线程都可以独立地对其进行操作,互不干扰。


ThreadLocal的内存泄漏举例

场景1

任务中不执行任何有意义的代码

//  将堆内存大小设置为-Xmx256m
public class ThreadLocalMemoryLeak {
    private static final int TASK_LOOP_SIZE = 500;

    /*线程池*/
    final static ThreadPoolExecutor poolExecutor
            = new ThreadPoolExecutor(5, 5, 1,
            TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());

    static class LocalVariable {
        private byte[] a = new byte[1024*1024*5];/*5M大小的数组*/
    }

    ThreadLocal<LocalVariable> threadLocalLV;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
            poolExecutor.execute(new Runnable() {
                public void run() {
//                    LocalVariable localVariable = new LocalVariable();
//                    ThreadLocalMemoryLeak oom = new ThreadLocalMemoryLeak();
//                    oom.threadLocalLV = new ThreadLocal<>();
//                    oom.threadLocalLV.set(new LocalVariable());
//                    oom.threadLocalLV.remove();
                    System.out.println("use local varaible");

                }
            });
        }
        System.out.println("pool execute over");
    }

}

执行后,我们通过cmd输入jvisualvm启动java性能监控工具(jdk自带),查看当前的性能消耗情况。

查看内存会发现内存消耗稳定在25MB左右。

场景2

在每个任务中new出一个数组,执行完成后我们可以看见,内存占用基本和场景1同

public class ThreadLocalMemoryLeak {
    private static final int TASK_LOOP_SIZE = 500;

    /*线程池*/
    final static ThreadPoolExecutor poolExecutor
            = new ThreadPoolExecutor(5, 5, 1,
            TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());

    static class LocalVariable {
        private byte[] a = new byte[1024*1024*5];/*5M大小的数组*/
    }

    ThreadLocal<LocalVariable> threadLocalLV;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
            poolExecutor.execute(new Runnable() {
                public void run() {
                    LocalVariable localVariable = new LocalVariable();
//                    ThreadLocalMemoryLeak oom = new ThreadLocalMemoryLeak();
//                    oom.threadLocalLV = new ThreadLocal<>();
//                    oom.threadLocalLV.set(new LocalVariable());
//                    oom.threadLocalLV.remove();
                    System.out.println("use local varaible");

                }
            });
        }
        System.out.println("pool execute over");
    }

}

启动后内存使用情况如下

 我们可以看到有毛刺现象,这是因为GC造成的,但是没有出现内存泄漏的情况。

场景3

启用Threadlocal

public class ThreadLocalMemoryLeak {
    private static final int TASK_LOOP_SIZE = 500;

    /*线程池*/
    final static ThreadPoolExecutor poolExecutor
            = new ThreadPoolExecutor(5, 5, 1,
            TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());

    static class LocalVariable {
        private byte[] a = new byte[1024*1024*5];/*5M大小的数组*/
    }

    ThreadLocal<LocalVariable> threadLocalLV;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
            poolExecutor.execute(new Runnable() {
                public void run() {
//                    LocalVariable localVariable = new LocalVariable();
                    ThreadLocalMemoryLeak oom = new ThreadLocalMemoryLeak();
                    oom.threadLocalLV = new ThreadLocal<>();
                    oom.threadLocalLV.set(new LocalVariable());
//                    oom.threadLocalLV.remove();
                    System.out.println("use local varaible");

                }
            });
            Thread.sleep(100);
        }
        System.out.println("pool execute over");
    }

}

启动后内存使用情况如下

占用内存情况在100MB-125MB左右。

场景4

手动调用remove清除threadlocal,执行查看内存情况。

public class ThreadLocalMemoryLeak {
    private static final int TASK_LOOP_SIZE = 500;

    /*线程池*/
    final static ThreadPoolExecutor poolExecutor
            = new ThreadPoolExecutor(5, 5, 1,
            TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());

    static class LocalVariable {
        private byte[] a = new byte[1024*1024*5];/*5M大小的数组*/
    }

    ThreadLocal<LocalVariable> threadLocalLV;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
            poolExecutor.execute(new Runnable() {
                public void run() {
//                    LocalVariable localVariable = new LocalVariable();
                    ThreadLocalMemoryLeak oom = new ThreadLocalMemoryLeak();
                    oom.threadLocalLV = new ThreadLocal<>();
                    oom.threadLocalLV.set(new LocalVariable());
                    oom.threadLocalLV.remove();
                    System.out.println("use local varaible");

                }
            });
            Thread.sleep(100);
        }
        System.out.println("pool execute over");
    }

}

启动后内存使用情况如下

此时内存使用情况跟场景1相同。 

总结:通过以上场景3,我们可以得出在启用了threadlocal以后发生了内存泄漏。


内存泄漏原因分析

        每一个Thread线程都维护了一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal 实例本身,value 是真正需 要存储的Object,也就是说ThreadLocal本身并不存储值,它只是作为一个 key来让线程从ThreadLocalMap获取value。ThreadLocalMap是使用ThreadLocal的弱引用作为Key的,弱引用的对象在GC 时会被回收。

        图中虚线表示弱引用。

        当把 threadlocal 变量置为 null 以后,没有任何强引用指向 threadlocal 实例,所以 threadlocal将会被gc回收。这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前 线程再迟迟不结束的话,这些key为null的Entry的 value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而这块value永远不会被访问到了,所以存在着内存泄露。

       只有当前线程结束后,线程就不会在栈中,强引用断开,才会被GC回收掉。解决内存泄漏做法是当不使用线程中threadlocal变量时及时remove清除数据。

       场景3中,虽然线程池里面的任务执行完毕了,但是线程池里面的5个线程会一直存在直到JVM退出,我们set了线程的localVariable变量后没有调用 localVariable.remove()方法,导致线程池里面的5个线程的threadLocals变量里面的new LocalVariable()实例没有被释放。当我们手动再每次都调用remove清除数据时,内存正常。

      ThreadLocal的实现中,无论是get()、set()在某些时候,调用了expungeStaleEntry方法用来清除Entry中Key为null的Value,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露。 只有 remove()方法中显式调用了expungeStaleEntry 方法。

总结

       JVM利用设置ThreadLocalMap的Key为弱引用,来避免内存泄露。

       JVM 利用调用remove、get、set方法的时候,回收弱引用。

       当ThreadLocal存储很多Key为 null的Entry的时候,而不再去调用 remove、 get、set 方法,那么将导致内存泄漏。

        使用线程池+ ThreadLocal 时要多注意,这种情况下,线程是一直在不断的重复运行的,从而也就造成了value可能造成累积的情况。

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

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

相关文章

互联网加竞赛 基于卷积神经网络的乳腺癌分类 深度学习 医学图像

文章目录 1 前言2 前言3 数据集3.1 良性样本3.2 病变样本 4 开发环境5 代码实现5.1 实现流程5.2 部分代码实现5.2.1 导入库5.2.2 图像加载5.2.3 标记5.2.4 分组5.2.5 构建模型训练 6 分析指标6.1 精度&#xff0c;召回率和F1度量6.2 混淆矩阵 7 结果和结论8 最后 1 前言 &…

RBAC基于角色的访问控制

一 什么是RBAC 概念 RBAC 是基于角色的访问控制&#xff08;Role-Based Access Control &#xff09;在 RBAC 中&#xff0c;权限与角色相关联&#xff0c;用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的&#…

Django 4.2.7 ORM 连接MySQLServer 完成单表CRUD

文章目录 Django ORM介绍1.使用pycharm新建一个Django项目2.修改settings.py文件中 DATABASES3.创建APP4.创建模型5.操作数据库 Django ORM介绍 Django 模型使用自带的 ORM。 对象关系映射&#xff08;Object Relational Mapping&#xff0c;简称 ORM &#xff09;用于实现面向…

Java程序员面试-场景篇

前言 裁员增效潮滚滚而来&#xff0c;特总结一些实际场景方案的面试题&#xff0c;希望对大家找工作有一些帮助。 注册中心 题目&#xff1a; 有三台机器&#xff0c;分别部署了微服务A、微服务B、注册中心&#xff0c;其中A和B都有服务接口提供并正常注册到了注册中心&…

SpringMVC概述

MVC介绍 MVC是一种设计模式&#xff0c;将软件按照模型、视图、控制器来划分&#xff1a; M&#xff1a;Model&#xff0c;模型层&#xff0c;指工程中的JavaBean&#xff0c;作用是处理数据 JavaBean分为两类&#xff1a; 一类称为实体类Bean&#xff1a;专门存储业务数据的&…

Docker Compose--部署SpringBoot项目--实战

原文网址&#xff1a;Docker Compose--部署SpringBoot项目--实战-CSDN博客 简介 本文用实战介绍Docker Compose部署SpringBoot项目。 ----------------------------------------------------------------------------------------------- 分享Java真实高频面试题&#xff0c…

Java方法用法及解析

在 Java 中&#xff0c;方法&#xff08;Method&#xff09;是用于执行特定任务的代码块。它是一个函数&#xff0c;用于封装一段可重复执行的代码&#xff0c;并可以被其他代码调用。方法定义了一系列操作的步骤&#xff0c;并提供了一种结构化和可复用的方式来组织和执行这些…

MathType7.6安装教程

1.软件介绍 MathType是一款可以帮助用户快速完成数学公式编辑的应用软件&#xff0c;这款软件适合在进行教育教学、科研机构、论文写作的时候使用。我们可以直接通过这款软件来获取到大量数学上使用到的函数、数学符号等内容&#xff0c;然后使用这些内容来完成公式编辑。 不管…

【mars3d】new mars3d.layer.GeoJsonLayer(实现环状面应该怎么传data

问题&#xff1a;【mars3d】new mars3d.layer.GeoJsonLayer(实现环状面应该怎么传data 解决方案&#xff1a; 1.在示例中修改showDraw()方法的data数据&#xff0c;实现以下环状面效果 2.示例链接&#xff1a; 功能示例(Vue版) | Mars3D三维可视化平台 | 火星科技 export f…

深入理解Java源码:提升技术功底,深度掌握技术框架,快速定位线上问题

为什么要看源码&#xff1a; 1、提升技术功底&#xff1a; 学习源码里的优秀设计思想&#xff0c;比如一些疑难问题的解决思路&#xff0c;还有一些优秀的设计模式&#xff0c;整体提升自己的技术功底 2、深度掌握技术框架&#xff1a; 源码看多了&#xff0c;对于一个新技术…

真的干不过,00后整顿职场已经给我卷麻了,想离职了...

在程序员职场上&#xff0c;什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗?也不是。技术很强的同事&#xff0c;可遇不可求&#xff0c;向他学习还来不及呢。 真正让人反感的&#xff0c;是技术平平&#x…

J2EE 实验一

实验一 基于Struts2的登录系统实现 一、目的与任务 目的&#xff1a;熟悉在集成开发平台上配置Struts2框架&#xff0c;学习Struts2编程 任务&#xff1a;在集成开发平台上创建Struts2项目&#xff0c;实现登录系统 二、内容、要求与安排方式 1、实验内容与要求&#xff1…

C盘突然满了,怎么清理

方法一 winr 输入%tem%按回车键&#xff0c;出现的这些都是缓存文件可以按删除键删掉 方法二 winr 输入cleanmgr按回车键&#xff0c;选择清理的盘符&#xff0c;这里选择C盘&#xff0c;点击确定删除 方法三 在系统设置里手动删除 找到电脑里面的设置选项&#xff0c;找…

第九节HarmonyOS 常用基础组件12-TextTimer

1、描述 通过文本显示计时信息并控制其计时器状态的组件。 2、接口 TextTimer(options?: {isCountDown?: boolean, count?: number, controller?: TextTimerController}) 3、参数 参数名称 参数类型 必填 描述 isCountDown boolean 否 是否倒计时。默认值&#…

LeetCode刷题--- 珠宝的最高价值

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 ​​​​​​http://t.csdnimg.cn/6AbpV 数据结构与算法 ​​​http://t.csdnimg.cn/hKh2l 前言&#xff1a;这个专栏主要讲述动…

如何启用Windows电脑的内置Administrator账户

前言 不知道从什么时候开始&#xff0c;新电脑或者新系统开机之后都会出现一个界面让你创建一个账户&#xff0c;但这个账户有可能是本地账户&#xff08;Windows10&#xff09;还有强制你登录微软账户的&#xff08;Windows11&#xff09;。 好像曾经熟悉的电脑Administrator…

750ml离心瓶进口国产离心机通用750ml离心杯高低速离心机瓶

750ml低速离心瓶系列&#xff1a; 产品货号&#xff1a;ZY1136222 材质&#xff1a;PPCO 容量&#xff08;ml&#xff09;&#xff1a;750 尺寸&#xff08;O.DxH,mm&#xff09;&#xff1a;98.5x147 最大离心力&#xff08;xg&#xff09;&#xff1a;6000 产品货号&…

Vue2:通过props给组件传数据

一、业务场景 我们在使用Vue组件时&#xff0c;常常会复用Vue组件&#xff0c;那么&#xff0c;问题来了&#xff0c;复用的时候&#xff0c;业务数据不相同&#xff0c;怎么办了&#xff1f; 这里我们就需要学习新的属性&#xff1a;props来实现这个功能。 这样&#xff0c;组…

代码随想录day23 二叉岁终章

669. 修剪二叉搜索树 题目 给定一个二叉搜索树&#xff0c;同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[L, R]中 (R>L) 。你可能需要改变树的根节点&#xff0c;所以结果应当返回修剪好的二叉搜索树的新的根节点。 思考 这题有个…

ComfyUI报错AttributeError: module ‘cv2.gapi.wip.draw‘ has no attribute ‘Text‘

ComfyUI在安装comfyui-reactor-node插件,然后启动之后突然报错: AttributeError: module cv2.gapi.wip.draw has no attribute Text 这是怎么回事呢? 于是四处搜寻答案。 总之就是opencv-python版本的问题导致的。 我将有可能解决办法的方法进行了总结。 下面列出所有解…