在最近一个项目中,在项目发布之后,发现系统中有内存泄漏问题。表象是堆内存随着系统的运行时间缓慢增长,一直没有办法通过gc来回收,最终于导致堆内存耗尽,内存溢出。开始是怀疑ThreadLocal的问题,因为在项目中,大量使用了线程的ThreadLocal保存线程上下文信息,在正常情况下,在线程开始的时候设置线程变量,在线程结束的时候,需要清除线程上下文信息,如果线程变量没有清除,会导致线程中保存的对象无法释放。
从这个正常的情况来看,假设没有清除线程上下文变量,那么在线程结束的时候(线程销毁),线程上下文变量所占用的内存会随着线程的销毁而被回收。至少从程序设计者角度来看,应该如此。实际情况下是怎么样,需要进行测试。
但是对于web类型的应用,为了避免产生大量的线程产生堆栈溢出(默认情况下一个线程会分配512K的栈空间),都会采用线程池的设计方案,对大量请求进行负载均衡。所以实际应用中,一般都会是线程池的设计,处理业务的线程数一般都在200以下,即使所有的线程变量都没有清理,那么理论上会出现线程保持的变量最大数是200,如果线程变量所指示的对象占用比较少(小于10K),200个线程最多只有2M(200*10K)的内存无法进行回收(因为线程池线程是复用的,每次使用之前,都会从新设置新的线程变量,那么老的线程变量所指示的对象没有被任何对象引用,会自动被垃圾回收,只有最后一次线程被使用的情况下,才无法进行回收)。
以上只是理论上的分析,那么实际情况下如何了,我写了一段代码进行实验。
- 硬件配置:
处理器名称: Intel Core i72.3 GHz 4核
内存: 16 GB
- 软件配置
操作系统:OS X 10.8.2
java版本:"1.7.0_04-ea"
- JVM配置
-Xms128M -Xmx512M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xloggc:gc.log
测试代码:Test.java
import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int testCase= Integer.parseInt(br.readLine()); br.close(); switch(testCase){ // 测试情况1. 无线程池,线程不休眠,并且清除thread_local 里面的线程变量;测试结果:无内存溢出 case 1 :testWithThread(true, 0); break; // 测试情况2. 无线程池,线程不休眠,没有清除thread_local 里面的线程变量;测试结果:无内存溢出 case 2 :testWithThread(false, 0); break; // 测试情况3. 无线程池,线程休眠1000毫秒,清除thread_local里面的线程的线程变量;测试结果:无内存溢出,但是新生代内存整体使用高 case 3 :testWithThread(false, 1000); break; // 测试情况4. 无线程池,线程永久休眠(设置最大值),清除thread_local里面的线程的线程变量;测试结果:无内存溢出 case 4 :testWithThread(true, Integer.MAX_VALUE); break; // 测试情况5. 有线程池,线程池大小50,线程不休眠,并且清除thread_local 里面的线程变量;测试结果:无内存溢出 case 5 :testWithThreadPool(50,true,0); break; // 测试情况6. 有线程池,线程池大小50,线程不休眠,没有清除thread_local 里面的线程变量;测试结果:无内存溢出 case 6 :testWithThreadPool(50,false,0); break; // 测试情况7. 有线程池,线程池大小50,线程无限休眠,并且清除thread_local 里面的线程变量;测试结果:无内存溢出 case 7 :testWithThreadPool(50,true,Integer.MAX_VALUE); break; // 测试情况8. 有线程池,线程池大小1000,线程无限休眠,并且清除thread_local 里面的线程变量;测试结果:无内存溢出 case 8 :testWithThreadPool(1000,true,Integer.MAX_VALUE); break; default :break; } } public static void testWithThread(boolean clearThreadLocal, long sleepTime) { while (true) { try { Thread.sleep(100); new Thread(new TestTask(clearThreadLocal, sleepTime)).start(); } catch (Exception e) { e.printStackTrace(); } } } public static void testWithThreadPool(int poolSize,boolean clearThreadLocal, long sleepTime) { ExecutorService service = Executors.newFixedThreadPool(poolSize); while (true) { try { Thread.sleep(100); service.execute(new TestTask(clearThreadLocal, sleepTime)); } catch (Exception e) { e.printStackTrace(); } } } public static final byte[] allocateMem() { // 这里分配一个1M的对象 byte[] b = new byte[1024 * 1024]; return b; } static class TestTask implements Runnable { /** 是否清除上下文参数变量 */ private boolean clearThreadLocal; /** 线程休眠时间 */ private long sleepTime; public TestTask(boolean clearThreadLocal, long sleepTime) { this.clearThreadLocal = clearThreadLocal; this.sleepTime = sleepTime; } public void run() { try { ThreadLocalHolder.set(allocateMem()); try { // 大于0的时候才休眠,否则不休眠 if (sleepTime > 0) { Thread.sleep(sleepTime); } } catch (InterruptedException e) { } } finally { if (clearThreadLocal) { ThreadLocalHolder.clear(); } } } } }
ThreadLocalHolder.java
public class ThreadLocalHolder { public static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(); public static final void set(byte [] b){ threadLocal.set(b); } public static final void clear(){ threadLocal.set(null); } }
- 测试结果分析:
无线程池的情况:测试用例1-4
下面是测试用例1 的垃圾回收日志
下面是测试用例2 的垃圾回收日志
对比分析测试用例1 和 测试用例2 的GC日志,发现基本上都差不多,说明是否清楚线程上下文变量不影响垃圾回收,对于无线程池的情况下,不会造成内存泄露
对于测试用例3,由于业务线程sleep 一秒钟,会导致业务系统中有产生大量的阻塞线程,理论上新生代内存会比较高,但是会保持到一定的范围,不会缓慢增长,导致内存溢出,通过分析了测试用例3的gc日志,发现符合理论上的分析,下面是测试用例3的垃圾回收日志
通过上述日志分析,发现老年代产生了一次垃圾回收,可能是开始大量线程休眠导致内存无法释放,这一部分线程持有的线程变量会在重新唤醒之后运行结束被回收,新生代的内存内存一直维持在4112K,也就是4个线程持有的线程变量。
对于测试用例4,由于线程一直sleep,无法对线程变量进行释放,导致了内存溢出。
有线程池的情况:测试用例5-8
对于测试用例5,开设了50个工作线程,每次使用线程完成之后,都会清除线程变量,垃圾回收日志和测试用例1以及测试用例2一样。
对于测试用例6,也开设了50个线程,但是使用完成之后,没有清除线程上下文,理论上会有50M内存无法进行回收,通过垃圾回收日志,符合我们的语气,下面是测试用例6的垃圾回收日志
通过日志分析,发现老年代回收比较频繁,主要是因为50个线程持有的50M空间一直无法彻底进行回收,而新生代空间不够(我们设置的是128M内存,新生代大概36M左右)。所有整体内存的使用量肯定一直在50M之上。
对于测试用例7,由于工作线程最多50个,即使线程一直休眠,再短时间内也不会导致内存溢出,长时间的情况下会出现内存溢出,这主要是因为任务队列空间没有限制,和有没有清除线程上下文变量没有关系,如果我们使用的有限队列,就不会出现这个问题。
对于测试用例8,由于工作线程有1000个,导致至少1000M的堆空间被使用,由于我们设置的最大堆是512M,导致结果溢出。系统的堆空间会从开始的128M逐步增长到512M,最后导致溢出,从gc日志来看,也符合理论上的判断。由于gc日志比较大,就不在贴出来了。
所以从上面的测试情况来看,线上上下文变量是否导致内存泄露,是需要区分情况的,如果线程变量所占的空间的比较小,小于10K,是不会出现内存泄露的,导致内存溢出的。如果线程变量所占的空间比较大,大于1M的情况下,出现的内存泄露和内存溢出的情况比较大。以上只是jdk1.7版本情况下的分析,个人认为jdk1.6版本的情况和1.7应该差不多,不会有太大的差别。
-----------------------下面是对ThreadLocal的分析-------------------------------------
对于ThreadLocal的概念,很多人都是比较模糊的,只知道是线程本地变量,而具体这个本地变量是什么含义,有什么作用,如何使用等很多java开发工程师都不知道如何进行使用。从JDK的对ThreadLocal的解释来看
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量, 它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。 |
ThreadLocal有一个ThreadLocalMap静态内部类,你可以简单理解为一个MAP,这个‘Map’为每个线程复制一个变量的‘拷贝’存储其中。每一个内部线程都有一个ThreadLocalMap对象。
当线程调用ThreadLocal.set(T object)方法设置变量时,首先获取当前线程引用,然后获取线程内部的ThreadLocalMap对象,设置map的key值为threadLocal对象,value为参数中的object。
当线程调用ThreadLocal.get()方法获取变量时,首先获取当前线程引用,以threadLocal对象为key去获取响应的ThreadLocalMap,如果此‘Map’不存在则初始化一个,否则返回其中的变量。
也就是说每个线程内部的ThreadLocalMap对象中的key保存的threadLocal对象的引用,从ThreadLocalMap的源代码来看,对threadLocal的对象的引用是WeakReference,也就是弱引用。
下面一张图描述这三者的整体关系
对于一个正常的Map来说,我们一般会调用Map.clear方法来清空map,这样map里面的所有对象就会释放。调用map.remove(key)方法,会移除key对应的对象整个entry,这样key和value 就不会任何对象引用,被java虚拟机回收。
而Thread对象里面的ThreadLocalMap里面的key是ThreadLocal的对象的弱引用,如果ThreadLocal对象会回收,那么ThreadLocalMap就无法移除其对应的value,那么value对象就无法被回收,导致内存泄露。但是如果thread运行结束,整个线程对象被回收,那么value所引用的对象也就会被垃圾回收。
什么情况下ThreadLocal对象会被回收了,典型的就是ThreadLocal对象作为局部对象来使用或者每次使用的时候都new了一个对象。所以一般情况下,ThreadLocal对象都是static的,确保不会被垃圾回收以及任何时候线程都能够访问到这个对象。
写了下面一段代码进行测试,发现两个方法都没有导致内存溢出,对于没有使用线程池的方法来说,因为每次线程运行完就退出了,Map里面引用的所有对象都会被垃圾回收,所以没有关系,但是为什么线程池的方案也没有导致内存溢出了,主要原因是ThreadLocal.set方法的实现,会做一个将Key== null 的元素清理掉的工作。导致线程之前由于ThreadLocal对象回收之后,ThreadLocalMap中的value 也会被回收,可见设计者也注意到这个地方可能出现内存泄露,为了防止这种情况发生,从而清空ThreadLocalMap中null为空的元素。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadLocalLeakTest { public static void main(String[] args) { // 如果控制线程池的大小为50,不会导致内存溢出 testWithThreadPool(50); // 也不会导致内存泄露 testWithThread(); } static class TestTask implements Runnable { public void run() { ThreadLocal tl = new ThreadLocal(); // 确保threadLocal为局部对象,在退出run方法之后,没有任何强引用,可以被垃圾回收 tl.set(allocateMem()); } } public static void testWithThreadPool(int poolSize) { ExecutorService service = Executors.newFixedThreadPool(poolSize); while (true) { try { Thread.sleep(100); service.execute(new TestTask()); } catch (Exception e) { e.printStackTrace(); } } } public static void testWithThread() { try { Thread.sleep(100); } catch (InterruptedException e) { } new Thread(new TestTask()).start(); } public static final byte[] allocateMem() { // 这里分配一个1M的对象 byte[] b = new byte[1024 * 1024 * 1]; return b; } }
相关推荐
在日常的开发和使用中,我们经常需要借助各种小工具来提高工作效率,例如快速启动常用的应用程序、管理文件等。一个简单但功能强大的集成工具箱可以帮助用户快速访问、启动并管理程序。今天,我们将以Python为基础,结合Tkinter和Win32API,开发一个类似Windows快捷方式的工具箱应用,能够让你轻松集成各种常用程序并一键启动
django自建博客app
《基于YOLOv8的智慧校园实验室高压灭菌锅安全联锁系统》(包含源码、可视化界面、完整数据集、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计
用于hifi测序数据的基因组组装程序
Microsoft Access 2010 数据库引擎可再发行程序包AccessDatabaseEngine-X64解压后的文件AceRedist
从大模型、智能体到复杂AI应用系统的构建——以产业大脑为例
自然语言处理之TF-IDF算法与TextRank算法的缠绵_textrank,tf-idf和两者的组合-CSDN博客.html
内容概要:2023版《科学智能 (AI4S)全球发展观察与展望》阐述了AI for Science(AI4S)在全球范围内的最新进展及其对科学和工业的深远影响。文章首先回顾了AI4S在过去一年中的快速发展,特别是在药物研发、材料科学、地质学、污染治理等多个领域的应用实例。AI4S通过结合深度学习、机器学习和其他AI技术,加速了从基础研究到实际应用的转化过程。例如,在药物研发中,AI4S帮助科学家克服了“反摩尔定律”的挑战,提高了新药研发的成功率;在材料科学中,AI4S实现了复杂材料的高效模拟,如人造钻石、石墨烯、碳纳米管等;在地质学中,AI4S通过模拟地球内部结构和物理过程,为地震学研究提供了新视角。此外,文章还探讨了大语言模型(LLMs)与科学方法的结合,指出LLMs不仅能辅助科学研究,还能生成新的科学假设并进行逻辑推理。 适合人群:具备一定科研背景或对AI技术感兴趣的科研人员、工程师、政策制定者及高校师生。
这个数据集包含了日常步数统计、睡眠时长、活跃分钟数以及消耗的卡路里,是个人健康与健身追踪的一部分。 该数据集非常适合用于以下实践: 数据清洗:现实世界中的数据往往包含缺失值、异常值或不一致之处。例如,某些天的步数可能缺失,或者存在不切实际的数值(如10,000小时的睡眠或负数的卡路里消耗)。通过处理这些问题,可以学习如何清理和准备数据进行分析。 探索性分析(发现日常习惯中的模式):可以通过分析找出日常生活中的模式和趋势,比如一周中哪一天人们通常走得最多,或是睡眠时间与活跃程度之间的关系等。 构建可视化图表(步数趋势、睡眠与活动对比图):将数据转换成易于理解的图形形式,有助于更直观地看出数据的趋势和关联。例如,绘制步数随时间变化的趋势图,或是比较睡眠时间和活动量之间的关系图。 数据叙事(将个人风格的追踪转化为可操作的见解):通过讲述故事的方式,把从数据中得到的洞察变成具体的行动建议。例如,根据某人特定时间段内的活动水平和睡眠质量,提供改善健康状况的具体建议。
框架结构天城商业办公楼5200平米(建筑图 结构图 计算书 开题报告 任务书 文献翻.zip
柴油机连杆加工工艺及夹具设计.zip
读书网首页的HTML信息
文字渐变颜色代码生成器:让文字绽放多彩魅力,演示:在信息交流日益丰富的今天,个性化的文字展示成为吸引目光的关键。这款文字渐变颜色代码生成器,便是为满足这一需求而生的绿色软件,无需安装,便捷实用。 它的操作极为简便。用户只需在软件界面中输入想要转换的文字内容,接着从丰富的色彩选项里挑选心仪的起始颜色与结束颜色,随后轻轻按下 “转换按钮”,神奇的事情就此发生 —— 适用于论坛、网页、QQ 空间等多种平台,以及自定义格式的渐变颜色代码便会即刻生成。不仅如此,生成的代码还能自动复制到剪切板,极大地节省了用户手动复制的时间。当你在论坛回帖、更新网页内容或是装扮 QQ 空间时,只需轻松粘贴代码,原本单调的文字瞬间就能拥有绚丽的渐变色彩,瞬间脱颖而出,为你的表达增添独特魅力,让文字不再平凡,轻松成为视觉焦点。 一款可以轻松把一段文字生成渐变颜色代码的绿色软件,当你在软件中输入完要转换的文字后,只需要挑选自己喜欢的起始颜色、结束颜色后,按一下―转换按钮即可生成相应的论坛/网页/QQ空间以及自定义格式代码,并且代码可以自动复制到剪切板中,回帖时直接粘贴代码即可不错得文字代码生成器,让你得文字更加漂亮.
1.【锂电池剩余寿命预测】Transformer锂电池剩余寿命预测(Matlab完整源码和数据) 2.数据集:NASA数据集,已经处理好,B0005电池训练、B0006测试; 3.环境准备:Matlab2023b,可读性强; 4.模型描述:Transformer在各种各样的问题上表现非常出色,现在被广泛使用。 5.领域描述:近年来,随着锂离子电池的能量密度、功率密度逐渐提升,其安全性能与剩余使用寿命预测变得愈发重要。本代码实现了Transformer在该领域的应用。 6.作者介绍:机器学习之心,博客专家认证,机器学习领域创作者,2023博客之星TOP50,主做机器学习和深度学习时序、回归、分类、聚类和降维等程序设计和案例分析,文章底部有博主联系方式。从事Matlab、Python算法仿真工作8年,更多仿真源码、数据集定制私信。
资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。
资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。
Android项目原生java语言课程设计,包含LW+ppt
配套文章:https://blog.csdn.net/gust2013/article/details/146909670?spm=1001.2014.3001.5502
《基于YOLOv8的智慧社区儿童游乐设施安全监测系统》(包含源码、可视化界面、完整数据集、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计