inputstreamreader未关闭会导致oom_ThreadLocal 一定会导致内存泄露?

e6ab48f77b7f41ca41f58786d7ffc953.png

在面试的时候,ThreadLocal作为高并发常用工具经常会被问到。而面试官比较喜欢问的问题有以下两个:

1、ThreadLocal是怎么实现来保证每个线程的变量副本的。

2、ThreadLocal的内存泄露是怎么产生的,怎么避免内存泄露。

首先我们来看第一个问题,实际上这个问题主要是想考察候选人是否有阅读过ThreadLocal的源码。当然阅读源码前我们得了解ThreadLocal是怎么使用的,只有使用过ThreadLocal我们才能产生对ThreadLocal是怎么做到的这样的疑问。而带着问题去阅读源码才能有一种廓然开朗的感觉。废话不多说,放码过来。

贴上threadlocal官方示例

public class ThreadId {     // Atomic integer containing the next thread ID to be assigned     private static final AtomicInteger nextId = new AtomicInteger(0);     // Thread local variable containing each thread's ID     private static final ThreadLocal threadId =         new ThreadLocal() {             @Override              protected Integer initialValue() {                 return nextId.getAndIncrement();             }     };      // Returns the current thread's unique ID, assigning it if necessary     public static int get() {         return threadId.get();     } }

上面代码主要通过从threadlocal得到该线程的id,如果当前线程没有的时候则生成一个。

通过上述例子我们会有如下疑问:

1、为什么ThreadLocal可以获取到当前线程的变量副本?

   -> 猜测ThreadLocal内部有一个Map对象(Map),key为线程对象,value为我们存储的变量。

带着上述疑问,我们看下ThreadLocal的源码实现。

先看set方法。

public class ThreadLocal {    public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }       ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }        void createMap(Thread t, T firstValue) {        t.threadLocals = new ThreadLocalMap(this, firstValue);    }    }

从getMap方法得知,ThreadLocal内部确实存在一个Map(ThreadLocalMap),只不过该map是线程的一个内部属性。而从createMap方法我们得知ThreadLocalMap是以ThreadLocal作为key,我们提供的值为value。

进入到Thread类源码查看,有两个ThreatLocalMap类型的属性。从注释可以看出,第一个是由ThreadLocal类维护的,第二个是由InheritableThreadLocal类维护的。而ThreadLocalMap是ThreadLocal的静态内部类。

public class Thread implements Runnable {  /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;    /*     * InheritableThreadLocal values pertaining to this thread. This map is     * maintained by the InheritableThreadLocal class.     */    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;  ......}

ThreadLocalMap内部维护的是一个Entry数组,而Entry数组继承WeakReference。我们知道,weakReference是当jvm内存不足时会回收只有WeakReference引用的对象。而ThreadLocalMap这样做的意图是为什么呢?这个问题会跟第二个疑问一起解答。

static class ThreadLocalMap {   static class Entry extends WeakReference<ThreadLocal>> {            /** The value associated with this ThreadLocal. */            Object value;            Entry(ThreadLocal> k, Object v) {                super(k);                value = v;            }        }        private Entry[] table;        ......}

如果是第一次看ThreadLocal源码的话,看到这里可能觉得有点绕,画个图理清一下关系。

dc81b5a19e5942efeece5e3c74ce3d67.png

首先创建了一个ThreadLocal对象

在一个线程里调用ThreadLocal的get方法,假设是第一次调用得到了null,这时我们在通过数据库查询得到value并调用set方法设置了进去,如果该Thread的threadlocals属性没有被初始化过则会执行ThreadLocal的createMap方法。

下次该线程执行调用get方法时就会从得到先set进去的值。

上面没有贴出get方法的代码,但是我们可以猜测出是通Thread.currentThread().threadLocals.get(this).value获取到值的。这里就不贴出来了。


第一个疑问解决了,现在看下面试问到的第二个问题。ThreadLocal的内存泄露是怎么产生的,怎么避免内存泄露?

网上搜ThreadLocal内存泄很多文章都会说到ThreadLocal的ThreadLocalMap的Entry数组继承的是WeakReference,而WeakReference会在jvm内存不足是回收引用。当thread常驻或者使用线程池时核心线程常驻thread未回收,而ThreadLocal被回收,但是value又是强引用,因此不会被回收而存在内存泄露。当我看了网上形形色色的文章都是这样的描述后仍然云里雾里,而且也不符合我们实际的使用场景。

产生这个问题主要有以下几个方面的原因:

一方面是我们虽然使用过ThreadLocal,但是对ThreadLocal会导致的内存泄露问题场景不熟悉导致的。

另一方面是我们虽然没有正确使用ThreadLocal,但是内存泄露问题并不足于导致OOM(或者说存在内存泄露的问题,但是并不致命)。

以下分几个场景讨论ThreadLocal内存泄露的问题。

场景一:一个请求对应一个新线程

这种场景下使用ThreadLocal,Thread里的threatLocals变量会随着线程的销毁而销毁,自然也就不存在内存泄露的问题了。

场景二:存在强引用的几个ThreadLocal + 少量核心线程数的线程池

使用ThreadLocal的场景中,我们多数都是定义为static类变量,但是并不是意味着只有static修饰才是强引用,只要有被别的类进行强引用的都算,只是定义为static类变量是我们比较常用的场景(参照官方例子)。

由于ThreadLocal存在外部强引用,因此ThreadLocalMap的key不会出现null的情况,而少量核心线程数意味着变量副本不会很多。因此内存泄露的量为 

s = coreThreadNums * (objectSize1 + objectSize2 + ... + objectSizeX), X = threadLocalNums

 因此当线程池核心线程数不多,threadLocal数量不多和threadLocal变量副本不大时,虽然存在内存泄露,但是却不足于导致OOM。但是当threadLocal变量副本比较大时内存泄露的情况就会严重,导致OOM的可能性加剧了。

场景三:无强引用的几个ThreadLocal + 少量核心线程数的线程池

解析跟场景二类似,只是threadlocalmap的key会变为null,但是value却不会被回收。因此还是存在内存泄露的情况。

场景二和场景三还有一个前提条件是线程池运行的task对threadlocal的使用是一次性的。因为当调用threadLocal的set、get和remove方法时会将threadlocalmap里key为null的entry的value释放掉(实际调用的是ThreadLocalMap的expungeStaleEntry方法,感兴趣的去看源码)。当然使用了强引用的ThreadLocal是享受不到该好处的。

场景四:无强引用的大量ThreadLocal 少量核心线程数的线程池

跟场景三类似,只是threadLocal作为threadlocal的key占用的内存稍微大了一点,

场景五:存在强引用的少量ThreadLocal大量核心线程数的线程池

解析跟场景二类型,由于每个常驻线程都有副本,因此内存泄露的情况加剧了。假设1000个核心线程,每个变量副本大小为1m,2个threadlocal则导致2g的内存泄露。

场景六:无强引用的少量ThreadLocal + 大量核心线程数的线程池

跟场景四类似。

场景七:无强引用ThreadLocal + 大量常驻线程的线程池服务于特定任务

这里的特定任务是指任务里都会用到threadlocal get或set 方法的任务,由于每次都调用了get或set方法,threadlocalmap会清理掉key为null的,但是当没有任务执行时,threadlocalmap的value仍然不会被回收,存在内存泄露。

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ThreadLocalTests {    public static void main(String[] args) throws Exception{        final ThreadLocal threadLocal1 = new ThreadLocal<>();        final ThreadLocal threadLocal2 = new ThreadLocal<>();        final ExecutorService threadPool = Executors.newFixedThreadPool(1000);        Thread.sleep(10000L);        for (int i = 0; i < 1000; i++){            threadPool.submit(() -> {                byte[] bytes = new byte[1024 * 1024];                threadLocal1.set(bytes);            });            threadPool.submit(() -> {                byte[] bytes = new byte[1024 * 1024];                threadLocal2.set(bytes);            });        }    }}

436f558387bbcc258e04d3220bf14a59.png

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ThreadLocalTests {    public static void main(String[] args) throws Exception{        final ThreadLocal threadLocal1 = new ThreadLocal<>();        final ThreadLocal threadLocal2 = new ThreadLocal<>();        final ExecutorService threadPool = Executors.newFixedThreadPool(1000);        Thread.sleep(10000L);        for (int i = 0; i < 1000; i++){            threadPool.submit(() -> {                byte[] bytes = new byte[1024 * 1024];                threadLocal1.set(bytes);                threadLocal1.remove();            });            threadPool.submit(() -> {                byte[] bytes = new byte[1024 * 1024];                threadLocal2.set(bytes);                threadLocal2.remove();            });        }    }}

64197ef2afb9523903af9e623990fc29.png

private void remove(ThreadLocal> key) {            Entry[] tab = table;            int len = tab.length;            int i = key.threadLocalHashCode & (len-1);            for (Entry e = tab[i];                 e != null;                 e = tab[i = nextIndex(i, len)]) {                if (e.get() == key) {                    e.clear();                    expungeStaleEntry(i);                    return;                }            }}
public void clear() {     this.referent = null;}

其他场景就不讨论了,可自行扩展。

d0f5cad08718f88c3d260b2ea468b599.png

这里做个总结。

无论threadlocal数量多少和常驻线程数量的多少都会导致内存泄露的问题,只是严重程度不同罢了。

那怎样才是使用ThreadLocal的正确姿势而不会导致内存泄露呢?这里举例zuul的requestContext例子。

zuul的requestContext

public class RequestContext extends ConcurrentHashMap<String, Object> {    protected static final ThreadLocal extends RequestContext> threadLocal = new ThreadLocal() {        @Override        protected RequestContext initialValue() {            try {                return contextClass.newInstance();            } catch (Throwable e) {                throw new RuntimeException(e);            }        }    };        /**     * unsets the threadLocal context. Done at the end of the request.     */    public void unset() {        threadLocal.remove();    }}
public class ZuulServlet extends HttpServlet {  @Override    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {        try {            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);            // Marks this request as having passed through the "Zuul engine", as opposed to servlets            // explicitly bound in web.xml, for which requests will not have the same data attached            RequestContext context = RequestContext.getCurrentContext();            context.setZuulEngineRan();            try {                preRoute();            } catch (ZuulException e) {                error(e);                postRoute();                return;            }            try {                route();            } catch (ZuulException e) {                error(e);                postRoute();                return;            }            try {                postRoute();            } catch (ZuulException e) {                error(e);                return;            }        } catch (Throwable e) {            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));        } finally {            // 实际执行threadlocal的清理方法            RequestContext.getCurrentContext().unset();        }    }}

只要在每次用完threadlocal后执行threadlocal的remove方法就可以清除掉变量副本,这样就不会产生内存泄露了。

ThreadLocalMap的remove方法,调用ThreadLocal的remove方法实际上是执行了ThreadLocalMap的remove方法。

private void remove(ThreadLocal> key) {            Entry[] tab = table;            int len = tab.length;            int i = key.threadLocalHashCode & (len-1);            for (Entry e = tab[i];                 e != null;                 e = tab[i = nextIndex(i, len)]) {                if (e.get() == key) {                    e.clear();                    expungeStaleEntry(i);                    return;                }            }        }

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

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

相关文章

keras保存模型_TF2 8.模型保存与加载

举个例子&#xff1a;先训练出一个模型import 接下来第一种方法&#xff1a;只保留模型的参数&#xff1a;这个有2种方法&#xff1a;model.save_weights("adasd.h5")model.load_weights("adasd.h5") model.predict(x_test)model.save_weights(./checkpoin…

第一章 Burp Suite 安装和环境配置

Burp Suite是一个集成化的渗透测试工具&#xff0c;它集合了多种渗透测试组件&#xff0c;使我们自动化地或手工地能更好的完成对web应用的渗透测试和攻击。在渗透测试中&#xff0c;我们使用Burp Suite将使得测试工作变得更加容易和方便&#xff0c;即使在不需要娴熟的技巧的情…

mysql57服务无法启动_将mysqld.service服务加入到systemctl

在开始安装二进制MySQL的时候感觉都还挺好&#xff0c;就是在启动服务的时候比较麻烦&#xff0c;一开始是在Centos6下的感觉也没有什么费劲的;但是在Centos7下面还是有点不太适应&#xff0c;不过还好用用就熟悉了&#xff1b;说明一下&#xff0c;我的安装目录在/usr/local/m…

linux raid autodetect,软raid的建立

1 增加磁盘并分区(修改id)fdisk /dev/sdbCommand (m for help): pDisk /dev/sdb: 8589 MB, 8589934592 bytes255 heads, 63 sectors/track, 1044 cylindersUnits cylinders of 16065 * 512 8225280 bytesDevice Boot Start End Blocks Id System/dev/sd…

c语言for循环的省略写法,C语言两种for循环写法分析

每个C程序员都知道同一个for循环语句可以有两种写法:A: for (i 0; i B: for (i cnt; i > 0; i--){ }前几天,DEBUG的时候, 发现采用A写法的代码反汇编出来有BUG.当时没有时间记录,环境也没有保存下来.今天尝试重现,又没来出现上次的问题...很奇怪.很久很久以前也听说过这两…

python文字游戏 生成数字菜单_pygame游戏之旅 游戏中添加显示文字

本文为大家分享了pygame游戏之旅的第5篇&#xff0c;供大家参考&#xff0c;具体内容如下 在游戏中添加显示文字&#xff1a; 这里自己定义一个crash函数接口&#xff1a; def crash(): message_diaplay(You Crashed) 然后实现接口函数message_display(text) def message_diapl…

springboot netty给特定客户端推送_Spring Boot 又升级了?2.0 你搞懂了吗?!

【小宅按】作为知名互联网公司都在用的技术&#xff0c;Spring Boot 2.0 的更新引起了很大的关注&#xff0c;本文将分为三部分解读 2.0 的更新&#xff1a;第一类&#xff0c;基础环境升级&#xff1b;第二类&#xff0c;默认软件替换和优化&#xff1b;第三类&#xff0c;新技…

OSI七层模型与TCP/IP五层模型详解

博主是搞是个FPGA的&#xff0c;一直没有真正的研究过以太网相关的技术&#xff0c;现在终于能静下心学习一下&#xff0c;希望自己能更深入的掌握这项最基本的通信接口技术。下面就开始搞了。 一、OSI参考模型 今天我们先学习一下以太网最基本也是重要的知识——OSI参考模型。…

android 自定义表情包,android基于环信的聊天和表情自定义

环信sdk的导入自定义聊天界面此处只有静态图&#xff0c;请谅解。自定义表情发送自定义聊天界面简单说下自定义的聊天界面&#xff0c;一个带有recyclerview和的xml文件&#xff0c;和对应的adapter即可。recyclerview为展示聊天信息。通过EMClient.getInstance().chatManager(…

erlang安装_RabbitMQ的使用(一)- RabbitMQ服务安装

作者&#xff1a;markjiang7m2博客园地址&#xff1a;https://www.cnblogs.com/markjiang7m2/p/12769627.html官网地址&#xff1a;http://letyouknow.netRabbitMQ&#xff0c;消息队列的一个中间件&#xff0c;这里不打算展开介绍了。此文意在记录工作中使用RabbitMQ时的过程及…

NodeJS React 开发环境搭建

1、首先需要安装NodeJS环境&#xff0c;下载NodeJS安装程序安装即可。 NodeJS下载地址&#xff1a; https://nodejs.org/en/download/ 2、安装NodeJS的web框架express npm install express-generator -g 3、创建项目 express studyReact 4、添加jsx引擎支持 npm install ex…

dreamweaver 正则表达式为属性值加上双引号_Python正则表达式(一)

Python正则表达式正则表达式是处理字符串的强大工具&#xff0c;拥有独特的语法和独立的处理引擎。我们在大文本中匹配字符串时&#xff0c;有些情况用str自带的函数(比如find, in)可能可以完成&#xff0c;有些情况会稍稍复杂一些(比如说找出所有“像邮箱”的字符串&#xff0…

mapperscan注解_SpringBoot 遗忘后的简单快速回忆之环境搭建与常见注解

原文作者&#xff1a;笑而抿之乎搭建SpringBoot环境&#xff0c;创建maven 项目后1&#xff0c;创建入口类&#xff1a;MapperScan(basePackages "com.baizhi.dao" ) //把dao层交给工厂管理SpringBootApplication//标识入口类的注解public class Applincation { …

Android插件丢失怎么办,Android studio推荐插件以及升级后插件丢失问题解决

1、android-butterknife-zeleznyandroid-butterknife-zelezny 是根据butterknife定制的一款插件&#xff0c;能够方便快速初始化&#xff0c;对于我来说是开发必备&#xff0c;本人也对此插件进行了一些优化&#xff0c;个人感觉用起来更爽 &#xff0c;博客地址&#xff1a;Bu…

软工团队 - 系统设计

软工团队 - 系统设计 修改完善需求规格说明书 针对栋哥在上周答辩中主要提到问题的相应改动 管理员层面没有在需求中得到很好的体现。没有手机号验证。那时候回答的比较含糊orz&#xff0c;所以在这里说明一下对此作出的解释和修改。 对于第一点&#xff0c;我们讨论的结果是至…

python decimal_python学习笔记一

1、~4不太明白、右移、左移整体移动添加零2、注意运算符&#xff0c;3、1<<5&716&704、set中的pop() 方法用于随机移除一个元素。字典中&#xff1a;list中5、Python dir() 函数dir()函数不带参数时&#xff0c;返回当前范围内的变量、方法和定义的类型列表&…

java基础基础总结----- Date

前言&#xff1a;其实在学习这个的时候&#xff0c;自我感觉学到什么直接查询API就可以了&#xff0c;没有必要再去研究某个方法怎么使用&#xff0c; 重点学习一下经常用到的方法。感觉自己的写的博客&#xff0c;就跟自己的笔记一样&#xff0c;用的是时候&#xff0c;就能快…

pandas object转float_Pandas中文官档~基础用法6

呆鸟云&#xff1a;“这一系列长篇终于连载完了&#xff0c;还请大家关注 Python 大咖谈&#xff0c;这里专注 Python 数据分析&#xff0c;后期呆鸟还会给大家分享更多 Pandas 好文。”数据类型大多数情况下&#xff0c;pandas 使用 Numpy 数组、Series 或 DataFrame 里某列的…

android studio 拉取分支,AndroidStudio中使用Git-高级篇(二)——新建分支(branch)和拉取请求(Pull request)...

前段时间写过一篇文章介绍如何在AndroidStudio使用上传项目到github&#xff0c;今天接着给大家带来了他的高级篇——新建分支(branch)和拉取请求(Pull request)。在真正的开发中我们很少写完代码commit后直接push代码上去&#xff0c;因为这样做没有经过第二个人的审核&#x…

collection转换为list_JAVA 集合 接口继承关系和实现,List,Set,Map(总结)

一. JAVA 集合1.接口继承关系和实现集合类存放于 Java.util 包中&#xff0c;主要有 3 种&#xff1a;set(集&#xff09;、list(列表包含 Queue&#xff09;和 map(映射)。1. Collection&#xff1a;Collection 是集合 List、Set、Queue 的最基本的接口。2. Iterator&#xff…