请问 LED三色警示灯怎么接线线

在最近一个项目中,在项目发布之后,发现系统中有内存泄漏问题。表象是堆内存随着系统的运行时间缓慢增长,一直没有办法通过gc来回收,最终于导致堆内存耗尽,内存溢出。开始是怀疑ThreadLocal的问题,因为在项目中,大量使用了线程的ThreadLocal保存线程上下文信息,在正常情况下,在线程开始的时候设置线程变量,在线程结束的时候,需要清除线程上下文信息,如果线程变量没有清除,会导致线程中保存的对象无法释放。
从这个正常的情况来看,假设没有清除线程上下文变量,那么在线程结束的时候(线程销毁),线程上下文变量所占用的内存会随着线程的销毁而被回收。至少从程序设计者角度来看,应该如此。实际情况下是怎么样,需要进行测试。
但是对于web类型的应用,为了避免产生大量的线程产生堆栈溢出(默认情况下一个线程会分配512K的栈空间),都会采用线程池的设计方案,对大量请求进行负载均衡。所以实际应用中,一般都会是线程池的设计,处理业务的线程数一般都在200以下,即使所有的线程变量都没有清理,那么理论上会出现线程保持的变量最大数是200,如果线程变量所指示的对象占用比较少(小于10K),200个线程最多只有2M(200*10K)的内存无法进行回收(因为线程池线程是复用的,每次使用之前,都会从新设置新的线程变量,那么老的线程变量所指示的对象没有被任何对象引用,会自动被垃圾回收,只有最后一次线程被使用的情况下,才无法进行回收)。
以上只是理论上的分析,那么实际情况下如何了,我写了一段代码进行实验。
硬件配置:
处理器名称: Intel Core i7&2.3 GHz &4核
内存: 16 GB
操作系统:OS X 10.8.2
java版本:”1.7.0_04-ea”
-Xms128M -Xmx512M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xloggc:gc.log
测试代码:Test.java&
import java.io.BufferedR
import java.io.InputStreamR
import java.util.concurrent.ExecutorS
import java.util.concurrent.E
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);
// 测试情况2. 无线程池,线程不休眠,没有清除thread_local 里面的线程变量;测试结果:无内存溢出
case 2 :testWithThread(false, 0);
// 测试情况3. 无线程池,线程休眠1000毫秒,清除thread_local里面的线程的线程变量;测试结果:无内存溢出,但是新生代内存整体使用高
case 3 :testWithThread(false, 1000);
// 测试情况4. 无线程池,线程永久休眠(设置最大值),清除thread_local里面的线程的线程变量;测试结果:无内存溢出
case 4 :testWithThread(true, Integer.MAX_VALUE);
// 测试情况5. 有线程池,线程池大小50,线程不休眠,并且清除thread_local 里面的线程变量;测试结果:无内存溢出
case 5 :testWithThreadPool(50,true,0);
// 测试情况6. 有线程池,线程池大小50,线程不休眠,没有清除thread_local 里面的线程变量;测试结果:无内存溢出
case 6 :testWithThreadPool(50,false,0);
// 测试情况7. 有线程池,线程池大小50,线程无限休眠,并且清除thread_local 里面的线程变量;测试结果:无内存溢出
case 7 :testWithThreadPool(50,true,Integer.MAX_VALUE);
// 测试情况8. 有线程池,线程池大小1000,线程无限休眠,并且清除thread_local 里面的线程变量;测试结果:无内存溢出
case 8 :testWithThreadPool(1000,true,Integer.MAX_VALUE);
public static void testWithThread(boolean clearThreadLocal, long sleepTime) {
while (true) {
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) {
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];
static class TestTask implements Runnable {
/** 是否清除上下文参数变量 */
private boolean clearThreadL
/** 线程休眠时间 */
private long sleepT
public TestTask(boolean clearThreadLocal, long sleepTime) {
this.clearThreadLocal = clearThreadL
this.sleepTime = sleepT
public void run() {
ThreadLocalHolder.set(allocateMem());
// 大于0的时候才休眠,否则不休眠
if (sleepTime & 0) {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
} finally {
if (clearThreadLocal) {
ThreadLocalHolder.clear();
ThreadLocalHolder.javapublic 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.ExecutorS
import java.util.concurrent.E
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) {
Thread.sleep(100);
service.execute(new TestTask());
} catch (Exception e) {
e.printStackTrace();
public static void testWithThread() {
Thread.sleep(100);
} catch (InterruptedException e) {
new Thread(new TestTask()).start();
public static final byte[] allocateMem() {
// 这里分配一个1M的对象
byte[] b = new byte[1024 * 1024 * 1];
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1842次
排名:千里之外
(1)(1)(2)(1)(2)ThreadLocal导致内存泄露
今天一早去上班的时候,登录上跑性能测试的服务器看了下,猛然一看又下了跳:好家伙,居然内存又长到了400m。我们写的个小小的Java服务器程序,怎么轻轻跑了了几个晚上内存就长成这样了呢。更何况我这几天还老是在看代码内可能存在的内存的问题,并且也找了几个内存泄露的问题。这让我这张老脸往哪里搁呢。
没办法,既然除了内存看起来不对的问题,那就还是来老一套吧,首选打开jvisualvm,连到目标进行,做了个一个heap
dump,然后将这个dump拷贝到开发机上慢慢上研究其中的奥秘。粗略一看系统内的实例的个数,数量特别大的都不是我们的写的类或者是我们直接使用的第三方库的类,这证明我们写的程序表面看起来是没有大问题。居然这样粗看不行,拿就只能挨个类挨个实例的看看到底是那些类的实例有异常了。首先排在前面的都是String
char[]等看不出毛病的东东,并且这些的类型的实例都有几十万个,是在是没办法期望能在其中找出导致有问题的GC
ROOT。再往下看,有一个CXF框架的类的intercepter相关的类居然有2w多个。这个可不正常,值得深入研究一下。
在我们的程序里只是使用一个叫IPF的框架调用了一个XDS的服务而已。XDS是基于Ebxml的,他的服务的实现也是基于WebService的,并且IPF框架也是基于CXF框架来实现WebService的调用。所以我们如果有问题的话应该就是我们这块的代码有问题了。首先是调出这些异常的类的并且找出其GC
Root来,奇怪的是这些事例居然都是被ThreadLocalMap管理的实例。
Java里每个Thread的实现都有一个ThreadLocalMap来辅助实现ThreadLocal的功能。ThreadLocal的实现是在Thread的ThreadLocalMap里面以ThreadLocal自己本身为key来保存值的,并且在ThreadLocalMap是对ThreadLocal的使用的WeakReference来保持对ThreadLocal的实例的gc的支持。这带来有两个后果,一是通过ThreadLocal来和线程绑定的值的应用是保存在Thread上的,这样当线程结束退出时,这个线程的所有的通过ThreadLocal塞进来的值都可以自动的被gc,二是ThreadLocal的实例本身是可以比起塞入某一个线程的ThreadLocalMap先被GC,而当这个ThreadLocal实例被GC后,通过其塞入到Thread的ThreadLocalMap的值将会变成一个不可被程序访问的但是又不会被GC的对象。根据条件一,这些对象在线程终止的时候是会自动被释放的,所以一般是不会有什么大问题的。但是如果是在一个长期存活的线程里,这些对象就是真正的内存的泄露了。在Java的程序界大家一致都有一个说法,就是要谨慎的使用ThreadLocal,相信也是跟他的这些特性来说的。我以前在使用ThreadLocal的时候,一般都是使用Try-finally来在finally块里强制解除threadlocal的值的绑定来释放内存。
回到问题中来,既然是ThreadLocal导致的问题则我就只有找到对象到GC
Root的对象的之间的所有类的代码来看看,看看到底是那个类存在使用了ThreadLocal并且存在问题。经过半天的摸索,发现在IPF的实现类里面有个类使用了一个ThreadLocal的成员变量来做缓存优化,即实现对每个线程只生成一个WebService的Client实例。但是在我们的程序里,每次调用XDS服务的代码的效果是每次都new了一个新的JaxWsClientFactory
的实例,并且由于Web
Server的线程池的效果,在我们高压力的性能可靠性的测试环境中,线程池中的线程线程基本上是长时间运行的,这就导致了不仅没有达到为每个线程只生成一个实例的优化效果,还导致了生成的代理Client不能被释放的问题,从而导致了内存的膨胀。修改的办法也比较简单,将JaxWsClientFactory
的实例做成单例的即可,这样可以保证JaxWsClientFactory 的优化效果的实现。
回过头来总结一下,总结了几点关于ThreadLocal的点:
谨慎使用ThreadLocal,如果使用的最好也是使用try-finally的方式来保证ThreadLocal对应的值会释放掉,既可以保证好的GC效果,也能最大可能的减少内存泄露的可能。
如果使用ThreadLocal来实现线程级的对象优化的功能,一定要特别处理在接口和文档上对内存谢咯的可能性的说明。最好使有一个close或者类似的方法来释放,否则使用者很容易会忽略其中的危险。并且对于使用这种封装优化机制的API,基本上只能起封装的类都是的实例都应该是单例才能有效的避免内存泄露。JaxWsClientFactory
的实现本身就是没有提供close之类的方法让人们来显式的释放对象。
顺便再说一句,线程池的存在真的对Java
EE程序来说就是并不每次服务器调用的结束后该线程就会结束,同志们一定要注意了。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。> 博客详情
写这篇的原因是看到一篇探讨ThreadLocal内存泄露问题的文章。无论是作者引用的别人的代码,还是作者自己写的代码,对ThreadLocal的使用都让我很惊讶(呃。。老外的文章一般都会这么说)。因为我是特别关注过ThreadLocal的,我在看书的时候曾把它当作一种过时的东西(书中似乎是这么说的),而实践过程中发现它却是个很好用。
首先看下Java6API文档中对它的描述(中文版):
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。 例如,以下类生成对每个线程唯一的局部标识符。线程 ID 是在第一次调用 UniqueThreadIdGenerator.getCurrentThreadId() 时分配的,在后续调用中不会更改。
import java.util.concurrent.atomic.AtomicI
public class UniqueThreadIdGenerator {
private static final AtomicInteger uniqueId = new AtomicInteger(0);
private static final ThreadLocal & Integer & uniqueNum =
new ThreadLocal & Integer & () {
@Override protected Integer initialValue() {
return uniqueId.getAndIncrement();
public static int getCurrentThreadId() {
return uniqueId.get();
} // UniqueThreadIdGenerator
每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
下面是我写的有关ThreadLocal内存回收的测试代码:
package misty.
import java.util.D
import java.util.concurrent.TimeU
* Created with IntelliJ IDEA.
* User: Misty
* Date: 13-3-14
* Time: 上午8:59
public class ThreadLocalTest {
private final static ThreadLocal&My50MB& tl = new ThreadLocal&My50MB&() {
protected My50MB initialValue() {
System.out.println(new Date() + " - init thread local value. in " + Thread.currentThread());
return new My50MB();
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
public void run() {
My50MB m = tl.get();
// do anything with m
}, "one");
Thread t2 = new Thread(new Runnable() {
public void run() {
My50MB m = tl.get();
// do anything with m
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}, "two");
t.start();
t2.start();
System.gc();
System.out.println(new Date() + " - GC");
t2.join();
System.gc();
System.out.println(new Date() + " - GC");
TimeUnit.MILLISECONDS.sleep(100);
public static class My50MB {
private String name = Thread.currentThread().getName();
private byte[] a = new byte[1024 * 1024 * 50];
protected void finalize() throws Throwable {
System.out.println(new Date() + " - My 50 MB finalized. in " + name);
super.finalize();
//Thu Mar 14 09:29:14 CST 2013 - init thread local value. in Thread[two,5,main]
//Thu Mar 14 09:29:14 CST 2013 - init thread local value. in Thread[one,5,main]
//Thu Mar 14 09:29:14 CST 2013 - GC
//Thu Mar 14 09:29:14 CST 2013 - My 50 MB finalized. in one
//Thu Mar 14 09:29:15 CST 2013 - GC
//Thu Mar 14 09:29:15 CST 2013 - My 50 MB finalized. in two&
那么ThreadLocal有何用呢?
很多时候我们会创建一些静态域来保存全局对象,那么这个对象就可能被任意线程访问到,如果它是线程安全的,这当然没什么说的。然而大部分情况下它不是线程安全的(或者无法保证它是线程安全的),尤其是当这个对象的类是由我们自己(或身边的同事)创建的(很多开发人员对线程的知识都是一知半解,更何况线程安全)。
这时候我们就需要为每个线程都创建一个对象的副本。我们当然可以用ConcurrentMap&Thread, Object&来保存这些对象,但问题是当一个线程结束的时候我们如何删除这个线程的对象副本呢?
ThreadLocal为我们做了一切。首先我们声明一个全局的ThreadLocal对象(final static,没错,我很喜欢final),当我们创建一个新线程并调用threadLocal.get时,threadLocal会调用initialValue方法初始化一个对象并返回,以后无论何时我们在这个线程中调用get方法,都将得到同一个对象(除非期间set过)。而如果我们在另一个线程中调用get,将的到另一个对象,而且始终会得到这个对象。
当一个线程结束了,ThreadLocal就会释放跟这个线程关联的对象,这不需要我们关心,反正一切都悄悄地发生了。
(以上叙述只关乎线程,而不关乎get和set是在哪个方法中调用的。以前有很多不理解线程的同学总是问我这个方法是哪个线程,那个方法是哪个线程,我不知如何回答。)
下面我们从实际角度出发。
假如我们需要在多线程环境下生成大量随机数,我们不会希望每次都创建一个Random对象,然后丢弃:
public void test1() {
Random r = new Random();
int i = r.nextInt();
} 最常见的做法是创建一个全局Random对象:
public final static Random rand = new Random();
public void test2() {
int i = rand.nextInt();
&幸运的是Random类的线程安全的,我们可以肆无忌惮的在多个线程中使用它。
然而在另一个需求中,我们就没那么幸运了——日期格式化DataFormat——我们每天都在跟日期格式化打交道,同样很多人是这么做的:
//第一种,效率低下
public void test1() {
DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(format.format(new Date()));
//第二种,单线程没问题,多线程等着哭吧
private final static DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd");
public void test2() {
System.out.println(FORMAT.format(new Date()));
//第三种,这是我自己加的,两年的工作中没见过这种写法,因为身边的同事会用synchronized关键字的都屈指可数
//这种方式依旧是效率低下(可能还不及第一种,没具体测试过)
public void test3() {
&& &synchronized (FORMAT) {
&& &&& &System.out.println(FORMAT.format(new Date()));
} DateFormat的API写得很清楚:
同步 日期格式不是同步的。建议为每个线程创建独立的格式实例。如果多个线程同时访问一个格式,则它必须保持外部同步。
保持外部同步已经在上述第三种方法中试过了,那么我们就为每个线程创建独立实例吧。
public final static ThreadLocal&DateFormat& TL_FORMAT = new ThreadLocal&DateFormat&() {
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
public void test4() {
DateFormat df = TL_FORMAT.get();
System.out.println(df.format(new Date()));
} 关于ThreadLocal的用法暂时探讨到这。
人打赏支持
码字总数 41290
呵呵,我目前只待过一个公司,不了解外边的情况。
当前线程结束后,会在栈中释放线程的引用,线程中对应的ThreadLocal.ThreadLocalMap对象会自动被释放掉的
每个线程要创建一个对象不错,不过一个程序中线程数一般都只有几十个,并且真正工作线程就更少了。例如一个web程序处理用户请求的线程数和系统的cpu核心数差不多,因此最多只是创建了几十个对象而已。而第一种方式是将创建对象放在处理方法中,即每次用户请求都要创建一个对象,一万次请求就创建了一万个对象。首先要理解web程序不会为每个用户请求创建单独的线程的,因为线程创建和切换的开销很大,而且系统线程数是有上限的,因此线程都是重复利用的。
而你下面说的和我最后举得例子有什么区别吗?
支付宝支付
微信扫码支付
打赏金额: ¥
已支付成功
打赏金额: ¥问题现象:
1 线程池中的线程,内存占用持续增长,通过 Jvisualvm 观察进程运行状态,发现 JVM 老年代内存最终会被耗尽,进而导致进程频繁 Full GC,CPU 资源几乎全部用于垃圾回收。
2 通过MAT(Memory Analyzer Tool)分析heapdump文件,发现线程对象的保留集(retained set)多大。线程池中有1500个线程,线程对象的堆内存几乎全部被 threadlocals 对象占用。
1 threadlocals 在什么情况下会造成内存泄露?
当 ThreadLocals 对象通过 set 方法复制后,没有通过remove方法销毁,且线程由线程池管理,生命周期比较长时,将必然发生内存泄露。
2 通过查看 VMware vSphere 6.0 官方文档,编写简单的Demo 实例,连接到网络中的虚拟环境,并获取信息。
调试发现,当 new VimService() 创建服务时,会创建 XMLStreamReaderImpl对象。
由图可见,在创建VimService对象时,创建了XMLInputFactoryImpl实例(ThreadLocal对象),但是服务创建后,该ThreadLocal对象并没有通过remove函数销毁,导致无法通过垃圾回收机制回收线程中的内存资源。
通过函数栈可发现,内存泄露并不是VimService直接导致的,而是 com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser 类额 parse() 函数在进行 xml 解析时,没有合理释放 ThreadLocal 对象资源。由于很难查找到相应版本的源码文件且时间有限,没有对源文件进行深入分析,也因此没有定位发生
ThreadLocal 相关内存泄露的根本原因。
3 虚拟化 2.0 VS 虚拟化 3.0
Vim2.5 使用了 new VimService() 来创建服务,而老版本通过 new VimServiceLocator() 创建服务。
这也是为什么老版本能正常运行,而新版本发生内存泄露。
解决方案:
1 通过 Vim2.5 api 释放 ThreadLocal 资源:
经过多次尝试,并没有找到合适的接口!
由于时间关系,没有深入阅读Vim2.5和WSServiceDelegate的源码,或许有更正规的方式释放资源,请网友们继续探索。
2 通过反射机制,在线程层面主动释放 ThreadLocal 对象:
线程池中的线程会反复调用实现Runnable的类的run()方法,来执行任务。ThreadLocal对象的生命周期本来就应该在 run() 方法执行完成后结束。现在通过反射来手动清楚 ThreadLocal 对象。
源码如下:
private void cleanThreadLocals() {
// Get a reference to the thread locals table of the current thread
Thread thread = Thread.currentThread();
Field threadLocalsField = Thread.class.getDeclaredField(&threadLocals&);
threadLocalsField.setAccessible(true);
Object threadLocalTable = threadLocalsField.get(thread);
// Get a reference to the array holding the thread local variables inside the
// ThreadLocalMap of the current thread
Class threadLocalMapClass = Class.forName(&java.lang.ThreadLocal$ThreadLocalMap&);
Field tableField = threadLocalMapClass.getDeclaredField(&table&);
tableField.setAccessible(true);
Object table = tableField.get(threadLocalTable);
// The key to the ThreadLocalMap is a WeakReference object. The referent field of this object
// is a reference to the actual ThreadLocal variable
Field referentField = Reference.class.getDeclaredField(&referent&);
referentField.setAccessible(true);
for (int i=0; i & Array.getLength(table); i++) {
// Each entry in the table array of ThreadLocalMap is an Entry object
// representing the thread local reference and its value
Object entry = Array.get(table, i);
if (entry != null) {
// Get a reference to the thread local object and remove it from the table
ThreadLocal threadLocal = (ThreadLocal)referentField.get(entry);
threadLocal.remove();
} catch(Exception e) {
// We will tolerate an exception here and just log it
throw new IllegalStateException(e);
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:3662次
排名:千里之外
原创:23篇
(1)(5)(8)(1)(4)(2)(1)(1)(1)

我要回帖

更多关于 灯的开关怎么接线 的文章

 

随机推荐