vmware Vim25 在线程池中造成(ThreadLocal)内存泄露

问题现象:

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() {
        try {
            // 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);
        }
    }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值