`

Of non-static ThreadLocals and memory leaks …

    博客分类:
  • Work
阅读更多

Of non-static ThreadLocals and memory leaks …

Tags: core, java, ThreadLocalRajiv @ 8:36 pm PDT

My recent experience has made me realize that the ThreadLocal class was never really designed to be used as a non-static field. However, the implications of making it non-static are not highlighted enough in JDK API and the many ThreadLocal tutorials you find on the net.

If you want to know more about ThreadLocals I would recommend reading Threading lightly by Brian Goetz. Brian talks about why and how to use ThreadLocals along with some examples. And if you are a performance buff, you will surely have an "Aha!" moment when you read the section on the performance bottleneck with the JDK 1.2 implementation and how it was resolved in JDK1.3.

Coming back to my "Non-static ThreadLocals considered harmful" [pun intended! ;)], couple of months ago Prasad called:

  • Prasad: Hey I need to store some information per thread per
    object
    can I do that?
  • Me: What?!
  • Prasad: See when we used a ThreadLocal for Transaction id,
    it was a static singleton across the VM. In my case I need the value of my
    ThreadLocal to be per instance of a cached object.
  • Me: Yeah, so you can have a non static ThreadLocal field in your cached
    object… right?!
  • Prasad: Yeah .. that was my question. Would that work?

What he wanted was something like:

Initial reference diagram

So I went about stepping through the code/logic to prove how/why it would work if you make the ThreadLocal non static. Well he tried the whole thing and it did work. Only months later did we realize that
there was a memory leak in the application! Running through optimizeIt and looking for non GC’ed instances we realized that the number of Context objects was more than the cache size. Likely that there was some one referring the Context even after the corresponding CachedObject had been removed from the Cache. OptimizeIt’s reduced reference graph showed that the ThreadLocals were holding reference to these Context object instances. Aha! Now there were multiple places where the CachedObjects were being evicted from the Cache. We had to set the ThreadLocal to null at all these places. 

The simplest option seemed to be to set the thread local to null in the finalize of the cached object. *buzzer* Well, unfortunately that wouldn’t work. The finalize method would get called in the Finalizer thread of the VM. So it would try to set the value to null in the Finalizer thread … but we wanted it to be set to
null in the Thread that accessed it!

So we refactored the code a bit, made sure all evictions happen in one place and made sure to set(null) on the ThreadLocal of the CachedObject which was going to be evicted. Unfortunately, that wasn’t the end of the story. We still had OutOfMem issues … only that it took a little longer now. OptimizeIt’s reduced reference graph showed that the number of CachedObjects was same as the Cache size now. So our solution did work. The memory leak was due to the number of ThreadLocals. Logically the number of ThreadLocals has to be same as the number of CachedObjects. So even after the CachedObject has been GC’ed, some one was holding a reference to the ThreadLocal. Initially, while stepping through the logic we had stopped the moment we concluded any thread would find the right value. We never went further to analyze what happens to all the ThreadLocals we created. We just assumed that GC would take care of it. [Which I still think was fair enough!]

When we invoke ThreadLocal.set(), the ThreadLocal would add itself as the key and the Context as the value to the threadLocal map of the current Thread. [This is in JDK 1.3 and above, the implementation is slightly different in JDk1.2, but the end result is the same ... there is a memory leak!] By setting the value of the ThreadLocal to null we have made the value of the map entry null . This made the Context to be available for garbage collection. However, the threadLocal map of Thread stills holds a reference to the key, which in our case is the ThreadLocal. We actually need to remove the ThreadLocal and not just set its value to null. If you are having trouble following this consider opening ThreadLocal.java and Thread.java from <jdk_1.3++_home>/src.zip and stepping through ThreadLocal.set and ThreadLocal.get methods. The memory leak was aggravated in this scenario because the web container reuses threads across requests and the cache objects keep getting created and garbage collected. However, the ThreadLocals don’t get GC’ed.

Unfortunately, it appears that till JDK1.5 came out, ThreadLocal’s did not have a proper lifecycle. They were expected to be created as static singletons or in limited numbers. For a proper life cycle I would have expected:

  1. The ThreadLocal should have taken care of cleaning itself up when no user
    code is referring to it. The way to achieve this is to make the threadLocals
    map of the Thread class a WeakHashMap. I wonder why this was not done …
    performance issues?! … or ThreadLocal was just not designed for non-static usage??
  2. An explicity destroy method should have been provided on ThreadLocal. In
    JDK 1.5, a new method remove
    has been added to ThreadLocal’s API to complete its lifecycle management.
    Users can call the remove method and avoid such memory leaks.

But for most of us who can’t make JDK 1.5 as the minimum requirement for our application we have to find some other way out.

One simple option seemed to be to write a CustomThreadLocal based on the JDK 1.2 implementation of ThreadLocal. Basically, have a synchronized Map of Thread vs threadLocalValue and expose a remove method which will remove the Thread from the map. However, this has serious performance issues, which is why ThreadLocal was redesigned in JDK 1.3! So the only realistic option is to make the
ThreadLocal a static field.

Currently each CachedObject instance has its own ThreadLocal instance and each ThreadLocal instance holds a Context instance. If we make the ThreadLocal static, then all the instances of CachedObject will refer to the same ThreadLocal instance. So, in order to retain the original semantics, we will have to make the value of the ThreadLocal to be a map of CachedObject vs Context. Since multiple threads will never access this map [which is local to the thread], this map does not need to be synchronized. So no performance bottlenecks and since the remove method is exposed, no memory issues. The new reference graph looks like: New reference diagram

and the source for the ThreadLocalMap would look like:

1
2    import java.util.*;
3
4    public class ThreadLocalMap
5            implements Map
6    {
7        private ThreadLocal threadLocal = new ThreadLocal();
8
9        private Map getThreadLocalMap(){
10           Map map = (Map) threadLocal.get();
11           if(map==null){
12               map = new HashMap();
13               threadLocal.set(map);
14           }
15           return map;
16       }
17
18       public Object put(Object key, Object value)
19       {
20           return getThreadLocalMap().put(key, value);
21       }
22
23       public Object get(Object key)
24       {
25           return getThreadLocalMap().get(key);
26       }
27
28       public Object remove(Object key)
29       {
30           return getThreadLocalMap().remove(key);
31       }
32
33       //code snipped for brevity ...
34
77   }
<noscript src="http://tellafriend.socialtwist.com:80/wizard/js/shoppr.core.js?id=2008071622"></noscript>
Share:
  • E-mail this story to a friend!
  • del.icio.us
  • description
  • Technorati
  • Reddit
  • Ma.gnolia
  • Google
  • YahooMyWeb
  • SphereIt
  • StumbleUpon
  • Digg
  • Mixx
  • TwitThis
  • Furl
  • Simpy
Related posts:
<!---->

No trackbacks

3 comments


  1. rajiv


    I have posted an update to this after receiving some feedback.

  2. Glenn Bech


    Great! Thanks for pointing out a potential application killer, and insight into the ThreadLocal lifecycle.


  3. Daniel Woo


分享到:
评论

相关推荐

    Detected memory leaks简单检测方法

    ### Detected Memory Leaks 简单检测方法 #### 背景介绍 在软件开发过程中,内存泄漏是一个常见的问题,特别是在使用C/C++等需要手动管理内存的语言进行开发时更为突出。内存泄漏不仅会导致程序占用的内存持续增长...

    FastReport Enterprise v4.15.6 for D4-XE5 installer〖含key〗

    - fixed memory leaks in font packing subsystem + Added Embarcadero RAD Studio XE5 support + Added Internal components for FireDac database engine + fixed bug with images in PDF export for OSX viewers ...

    FastReport Enterprise v4.15.6 for XE5 installer

    - fixed memory leaks in font packing subsystem + Added Embarcadero RAD Studio XE5 support + Added Internal components for FireDac database engine + fixed bug with images in PDF export for OSX viewers ...

    FastReport Enterprise v4.15.6 for D7

    - fixed memory leaks in font packing subsystem + Added Embarcadero RAD Studio XE5 support + Added Internal components for FireDac database engine + fixed bug with images in PDF export for OSX viewers ...

    FastReport Enterprise v4.15.6 for XE-XE3 installer

    - fixed memory leaks in font packing subsystem + Added Embarcadero RAD Studio XE5 support + Added Internal components for FireDac database engine + fixed bug with images in PDF export for OSX ...

    FastReport Enterprise v4.15.6 for XE4-XE5 installer

    - fixed memory leaks in font packing subsystem + Added Embarcadero RAD Studio XE5 support + Added Internal components for FireDac database engine + fixed bug with images in PDF export for OSX viewers ...

    FastReport Enterprise v4.15.6 for 2005-2010 installer

    - fixed memory leaks in font packing subsystem + Added Embarcadero RAD Studio XE5 support + Added Internal components for FireDac database engine + fixed bug with images in PDF export for OSX ...

    ember-memory-leaks-codemod:用于修复Ember应用程序中的内存泄漏的codemod的集合

    用法要从该项目运行特定的codemod,您将运行以下命令: npx ember-memory-leaks-codemod &lt;TRANSFORM&gt; path/of/files/ or/some**/*glob.js# oryarn global add ember-memory-leaks-codemodember-memory-leaks-codemod...

    FastReport Enterprise v4.15.6 for DELPHI4-2006 installer

    - fixed memory leaks in font packing subsystem + Added Embarcadero RAD Studio XE5 support + Added Internal components for FireDac database engine + fixed bug with images in PDF export for OSX ...

    Bounds Checker6.01 for Delphi

    Resource Leak Detection BoundsChecker automates the process of locating difficult-to-find memory leaks, and monitors heap, static and stack memory. This saves you time and frustration, and enables ...

    common-sense-c-advice-and-warnings-for-c-and-c-programmers.9781882419005.32087

    Topics covered include pointer arithmetic, dereferencing, and common pitfalls like dangling pointers and memory leaks. The chapter also provides practical tips for working with pointers, including ...

    as-21-Shi-Mining-And-Exploiting-(Mobile)-Payment-Credential-Leak

    as-21-Shi-Mining-And-Exploiting-(Mobile)-Payment-Credential-Leaks-In-The-Wild(1)

    论文研究-A Tool for Testing Java Memory Leaks.pdf

    标题《论文研究-A Tool for Testing Java Memory Leaks.pdf》和描述《一个测试Java内存泄漏的工具,党万春,钱巨,虽然Java语言有自带的垃圾回收机制,Java程序仍然存在由无效引用带来的内存泄漏。而内存泄漏会导致...

    IoT-detection-fire-gas-or-smoke-leaks-and-temperature

    物联网 #设备: #DHT11 Sensor #NodeMCU ESP8266 WiFi CH340 #Flame Sensor#MQ2 Sensor #Jumper #Active Buzzer #Arduino IDE #Blynk应用 #原理图设计

    Objective C Memory Management Essentials(PACKT,2015)

    You will begin with a basic understanding of memory management, and why memory leaks occur in an application, moving on to autorelease pools and object creation/storage to get an idea of how memory ...

    java面试题英文版及其答案

    13. What is the difference between static and non-static members in Java?Answer: Static members belong to the class itself and are shared among all instances of the class. They can be accessed without...

    acpi控制笔记本风扇转速

    memory, instead of performing a bytewise read. (Region must be of type SystemMemory, see below.) Fixed the Load ASL operator for the case where the source operand is a region field. A buffer object ...

    ML-Leaks-master.zip

    "ML-Leaks-master.zip"这个压缩包包含的代码详细展示了如何进行这种攻击,对于理解和防范此类安全问题具有重要意义。 成员推断攻击是一种针对深度学习模型的隐私侵犯手段。攻击者尝试通过模型的输出来判断某个特定...

    CPU-Z 1.47,CPU 查看工具

    -core=id : Displays clock speed of core #id (id can be set from 0 to Number of cores minus one). Keys ---- F5 : save the current tab in a bmp file F6 : save the current tab in the clipboard F7 : ...

    Finding memory leaks发现内存的泄漏(6KB)

    这篇“Finding memory leaks发现内存的泄漏”可能是关于如何定位和解决内存泄漏的技术指南。 在C++编程中,程序员需要手动管理内存,通过`new`关键字申请内存,然后通过`delete`关键字释放内存。如果忘记释放或错误...

Global site tag (gtag.js) - Google Analytics