0 写在前边

今天以 “TheadLocal 为什么会导致内存泄漏” 为题与朋友们讨论了一波,引出了一些原理性的内容,本文就这个问题作答,并扩展相关的知识点

1 ThreadLocal 和 ThreadLocalMap 是什么?

简单来说,ThreadLocal 是一种操作与线程绑定的共享对象的工具,通过ThreadLocal可以将一些对象保存在线程上,实现同线程不同方法之间的对象共享。

线程的上下文由 ThreadLocalMap 组成,它是 ThreadLocal 的静态内部类,存储着线程共享对象。

一般来说,我们无需显式创建ThreadLocalMap,也无需为装入ThreadLocalMap 对象设 key 值,因为在 set 方法执行时会创建 ThreadLocalMap,并将当前 ThreadLocal 对象作为 key,待存储对象作为 value,存储到 ThreadLocalMap。

值得一提的是,ThreadLocalMap 的 key 与 value 的类型是不同的,key 是弱引用类型的,value 是强引用类型的。

2 Thread、ThreadLocal 与 ThreadLocalMap 之间的关系

Thread 与 ThreadLocalMap

首先 ThreadLocalMap 是与 Thread 进行绑定的,ThreadLocalMap 是线程上实际存储共享对象的容器。

如下图,threadLocals 就是默认的 ThreadLocalMap,默认为 null

绑定 ThreadLocalMap 到 Thread 的位置在 ThreadLocal 的 createMap 方法中,threadLocals 引用指向 ThreadLocalMap。(这里还包含了放置第一个对象的操作)

ThreadLocal 的 getMap 方法取的就是线程的 threadLocals

ThreadLocal与ThreadLocalMap

ThreadLocalMap 是 ThreadLocal 类的静态内部类,ThreadLocal 是操作 ThreadLocalMap 的工具,还是 ThreadLocalMap 的 key 对象,在 ThreadLocal 作为 key 保存前转换成弱引用类型。

一般我们通过 ThreadLocal 的 set 方法进行保存对象,在 set 方法内部获取了当前线程的 ThreadLocalMap,调用 ThreadLocalMap 的 set 方法进行保存对象。

使用 this 关健字将当前使用的 ThreadLocal 对象作为 key 存到 ThreadLocalMap 中,以减小 key 冲突的可能性。

ThreadLocalMap 中的 set 方法主要是创建一个 Entry 对象放进数组中,Entry 继承 WeakReference 类,将 Entry 的 key(也就是 ThreadLocal)转成弱类型。

一句话总结它们之间的关系

每个 Thread 绑定 ThreadLocalMap 来存储线程上下文共享对象,ThreadLocalMap 中的key(即,ThreadLocal)在同一线程中是唯一的。单线程情况下,每个 ThreadLocal 只对应一个值对象。

3 ThreadLocal导致的内存泄漏的原因是什么?

导致内存泄漏的原因在于程序员未在使用完ThreadLocalMap中存储的对象后清除这些对象。

ThreadLocalMap是维护在Thread内部的,意味着只要线程不退出,ThreadLocalMap中保存的对象引用就会一直存在,由于垃圾回收器是依据可达性分析的,存在强引用的对象不会被回收,而ThreadLocalMap中存储的对象都是强引用的。

假设当前线程处于一个死循环中(比如,Tomcat),随着ThreadLocalMap保存的对象越来越多,垃圾收集器无法回收强引用的对象,就会导致可用堆内存越来越小,出现内存泄漏,最终抛出OOM。

4 如何清理 ThreadLocalMap 存储的对象?

用完 ThreadLocal 存储的对象后,只需调用 ThreadLocal 的 remove 方法,就会自动将 ThreadLocalMap 中的 K-V 对引用置空,垃圾收集器会在合适的时机内清除 K-V 对象释放内存。

ThreadLocal 类 remove 方法,获取当前线程上的 ThreadLocalMap 移除以此 ThreadLocal 为 key 的对象。通过调用 ThreadLocalMap 的 remove 方法实现。

ThreadLocalMap 的 remove 方法中,e.clear() 调用的是key对象继承的 Reference 类的 clear(),对 key 引用置空,expungeStaleEntry(i) 对 value 引用置空。

ThreadLocalMap 的 expungeStaleEntry 方法,分别取出 ThreadLocalMap 中的 Entry 的 value 与 Entry 本身先后置空。

5 为什么ThreadLocalMap使用弱引用key?

ThreadLocalMap 是与线程绑定的,线程不退出,强引用的key对象就不会被垃圾回收,当用户妥善处理的无用K-V对象就会导致内存泄漏。利用弱引用可以及时被 GC 的特性,回收绝大多数key(除 static 域的全局 key 外),以减缓内存泄漏。

实际上最需要回收的是value对象,弱引用key只是一种挽救措施。

6 ThreadLocalMap 为什么使用强引用 value,而不是弱引用?

与 key 不同的是,key 仅作为索引,实际工作的是 value,value 需要共享。

当局部 value 对象所在的方法结束,栈桢被清空时,会将局部 value 对象引用销毁,垃圾收集器会清除没有引用的对象。

如果此时设置成弱引用装入 Map,value 对象会在某次 GC 时消亡,这肯定不是我们希望的。

我们希望的是value对象可以维持存活以共享,只有强引用可以达到目的。

7 线程池会累积 ThreadLocalMap 的占用的内存而出现内存泄漏吗?

解释下问题,之前有讲过,ThreadLocalMap 与 Thread 的生命周期是一致的,而线程池技术是复用线程的,如果之前的 ThreadLocalMap 已经开始内存泄漏,是否会出现累积已泄漏的内存?

线程池不存在这个问题,虽然它复用了线程,但是清除了上一线程的所有资源。

8 线程有一个ThreadLocalMap,ThreadLocal也只有一个值,为何还会内存泄漏?

这是我自己思考时提出来的,能问出这个问题,只能说当时还没完全理解ThreadLocal与ThreadLocalMap的对应关系。

原问题:一个线程有一个ThreadLocalMap(不考虑继承ThreadLocal的那个实现),即然 ThreadLocal 作为 key 了,那么ThreadLocalMap中是否只会有一个Entry,内存再泄露能泄露到哪里去?(误认为ThreadLocalMap与ThreadLocal绑定,只有一个,也只能装一个Entry,这是错误的)

其实 ThreadLocal 我们可以创建很多个,ThreadLocalMap却只有一个(不考虑继承ThreadLocal的那个实现),通过创建多个 ThreadLocal 来存取 ThreadLocalMap 中的对象。

伪代码举例:

ThreadLocal<A> aThreadLocal = new ThreadLocal<A>();
ThreadLocal<B> bThreadLocal = new ThreadLocal<B>();
aThreadLocal.set(new A("a"));
bThreadLocal.set(new B("b"));
aThreadLocal.get();
bThreadLocal.get();

我在ThreadLocal的getMap()打了断点,当前线程中 ThreadLocalMap 中有两个对象,可以看到referent中记录了保存对象的ThreadLocal对象的HashCode。这起码证明了ThreadLocalMap不仅仅能装一个对象

9 【扩展】Java对象的引用类型

  • 强引用:常见new的对象,只要还有强引用的对象,则不会被GC
  • 软引用:比强引用弱,仅当JVM内存不足时才会清理,清理时机在OOM前
  • 弱引用:只提供非强制的映射关系,会被JVM择机清理
  • 虚引用(幻象引用):无法通过它访问对象,只确保对象在finalize后执行某些操作

iOS打包测试ipa的更多相关文章

  1. iOS打包测试

    iOS开发中,怎么打包为ipa格式的软件包以及怎么安装到手机终端上. (我的开发环境为Xcode6.1.1 )作为个人开发者(当然我的开发者帐号为个人开发者帐号,而不是企业开发者帐号),对于自己来说, ...

  2. iOS打包为ipa的两种方式和生成P12证书的方式

    iOS项目打包为ipa的两种方式: 准备工作:先行在Xcode里面打开preferences,填写apple id. 通过iTunes+Xcode 在Xcode里,把模拟器调整为iOS Device, ...

  3. iOS 打包 测试 发布

    1.企业版 1.1 打包 1.1.1 使用apple企业账号 获取 证书cer,描述文件provision (开发 生产) *注: 描述文件 又 三者组成(cer + appId + bundleId ...

  4. iOS 打包生成ipa文件(使用终端命令打包)

    1. 打开终端 2.在终端输入cd +空格 把工程文件直接拖到终端,然后回车 3. 在终端输入xcodebuild,回车 然后你可以发现工程文件里多了一个build文件夹 在build文件夹中,有一个 ...

  5. iOS打包(ipa包)

    1.打开XCode打开project文件.选择Product,再点击Archive. 2.鼠标右键点击Shoe In Finder 3.鼠标右键选择"显示包内容" 4.鼠标左键双击 ...

  6. hybird app 用 xcode ios打包 ipa 测试包并且安装真机测试

    1.创建 ios 项目 1.用 cordova 创建一个 ios 项目 npm install -g cordova cordova create hello com.mydomain.hello H ...

  7. Unity使用Xcode将项目打包成IPA

    https://blog.csdn.net/Superficialtise/article/details/79699813 Unity是个开放性的平台,打包时也可以选择多种打包类型,几乎包含了所有的 ...

  8. [iOS]如何把App打包成ipa文件,然后App上架流程[利用Application Loader]

    摘自:http://www.cnblogs.com/wangqi1221/p/5240281.html 在上一篇博客已经讲过上传项目了,但是有的时候,需要我们进行打包成ipa包到别的手机上跑(但是前提 ...

  9. Unity3D研究院之IOS全自动打包生成ipa

    接着上一篇文章, 自动生成framework,这篇文章我把shell自动化打包ipa整理了一下,希望大家喜欢,嘿嘿.. 建议大家先看一下上一篇文章.http://www.xuanyusong.com/ ...

随机推荐

  1. Docker文件系统实战

    关键词:Docker 联合文件系统 镜像 容器 云信私有化 在本文中,我们来实战构建一个Docker镜像,然后实例化容器,在Docker的生命周期中详细分析一下Docker的文件存储情况和Docker ...

  2. Linux 递归批量删除文件夹或文件的命令

    笔者有一次Linux服务器上的网站被别人在每一个文件夹下恶意注入了目录和文件,由于项目的目录太多,手动删除费时费力,最后用Linux的命令批量删除了注入内容.相关命令介绍如下: 递归批量删除文件夹: ...

  3. h5页面自动播放视频、音频_关于媒体文件自动全屏播放的实现方式

    在移动端(ios和android)播放视频的时候,我们即使定义了autoplay属性,仍然不能自动播放.这是由于手机浏览器为了防止浪费用户的网络流量,在默认情况下是不允许媒体文件自动播放的,除非用户自 ...

  4. 【Python】any() 或者 or

    前言 在我之前的文章中有any()和all()的对比:any()和all()对比其中介绍了any()函数的基本特性---可迭代对象中有任意一个不为False的时候,返回True,如果可迭代对象为空的话 ...

  5. 「从零单排canal 04」 启动模块deployer源码解析

    基于1.1.5-alpha版本,具体源码笔记可以参考我的github:https://github.com/saigu/JavaKnowledgeGraph/tree/master/code_read ...

  6. Azure Web App (一)发布你的Net Core Web 项目

    一,引言 今天我们看一下Azure上的一个服务-----Web 应用,我们都知道云计算的三大模式:Iaas(基础设施即服务),Paas(平台即服务),Saas(软件即服务). Iass,其实就是虚拟主 ...

  7. JS的数据类型你真的懂了吗

    一.JS数据类型分类 1.基本数据类型 (1)Number 数字 (2)String 字符串 (3)Boolean 布尔值 (4)null 空对象指针 (5)undefined 为定义 (6)symb ...

  8. 简单shellcode学习

    本文由“合天智汇”公众号首发 作者:hope 引言 之前遇到没开启NX保护的时候,都是直接用pwtools库里的shellcode一把梭,也不太懂shellcode代码具体做了些什么,遇到了几道不能一 ...

  9. Java入门系列之重写

    前言 关于所有Java系列文章面向有一定基础的童鞋,所写每一篇希望有一定含金量,有些内容可能会从Java整个语法全局考虑穿插后续要讲解的内容以成系统,若不理解,请看完后再学习.上一节我们讲解完了fin ...

  10. 1-GPIO

    GPIO的配置: GPIO库函数编程: void LED_init(void)//LED初始化 { GPIO_InitTypeDef GPIO_InitStructure;//定义一个结构体变量 RC ...