Hatred's Log Place

DON'T PANIC!

Feb 8, 2011 - 2 minute read - programming

ThreadLocal Memory Leak

Использовать ThreadLocal переменные удобно, для расшаривания данных между разными частями одного потока (допустим достучаться из POJO объекта к HTTP сессии и т.п.). Другое дело, что чревато в контексте использования сервера приложений: тут треды не уничтожаются, а возвращаются в пул потоков, как следствие: GC не собирает их, они остаются в памяти, а тут, помимо явной утечки, есть ещё и потенциальная дырка в безопасности.

По ссылке статья на английском на эту тематику. Как решить теперь буду думать.

По этой ссылке:

http://wiki.apache.org/tomcat/MemoryLeakProtection

Можно почитать про разные MemLeaks в веб-приложениях (точнее про технологию защиты от оных в Tomcat)

UPD: подкатом решение (???)

Решение

Найдено тут: http://weblogs.java.net/blog/jjviana/archive/2010/06/09/dealing-glassfish-301-memory-leak-or-threadlocal-thread-pool-bad-ide

какое выбирать, решать вам, я выбрал второй вариант с наследованием класса. Скопирую его тут с форматированием:


import java.lang.ref.SoftReference;

public abstract class SoftThreadLocal<T> extends ThreadLocal<T>
{
    // Encapsulation required because Generics is stupid about references, and there is no interface for ThreadLocal, 
    //so I can't simply extend a single ThreadLocal, grr! Java has so many brittle design mistakes in it.
    private final ThreadLocal<SoftReference<T>> local = new ThreadLocal<SoftReference<T>>();

    
    @Override
    public T get() 
    {
        SoftReference<T> ref = local.get();
        T result = null;
        
        if (null != ref) 
        {
            result = ref.get();
        }
        
        if (null == result) 
        {
            result = initialValue();
            ref = new SoftReference<T>(result);
            local.set(ref);
        }
        
        return result;
    }
    
    
    @Override
    public void set(T value) 
    {
        if (null == value) 
        {
            remove();
        } 
        else 
        {
            local.set(new SoftReference<T>(value));
        }
    }
    
    
    @Override
    public void remove() 
    {
        local.remove();
    }
}

Далее работа классическая, к примеру, как у меня через ThreadLocal Singleton:

import java.util.HashMap;

/**
 * Небольшой вспомогательный статический класс
 * для расшариванить локальных для треда переменных. Значение будет для каждого треда своё.
 * 
 * Допустим, получая в методе doPost()/doGet() сервлета значение сессии и для проброса во все дочерние
 * классы, без необходимости передачи оных параметром, что, собственно говоря, не всегда возможно.
 *
 * @author hatred
 * 2011-01-27
 */
public class ThreadContext
{
    private /*static*/ final SoftThreadLocal<HashMap<Object, Object>> _context = new SoftThreadLocal<HashMap<Object, Object>>()
    {
        protected HashMap<Object,Object> initialValue()
        {
            return new HashMap<Object,Object>();
        }
    };

    private static ThreadContext _instance = null;
    //
    
    synchronized public static ThreadContext getContext()
    {
        if (_instance == null)
        {
            _instance = new ThreadContext();
        }
        
        return _instance;
    }
    
    synchronized public static void releaseContext()
    {
        if (_instance != null)
        {
            _instance.clear();
            _instance.contextRemove();
        }
        
        _instance = null;
    }
    
    public void put(Object key, Object value)
    {
        _context.get().put(key, value);
    }
    
    public Object get(Object key)
    {
        return _context.get().get(key);
    }
    
    public void remove(Object key)
    {
        _context.get().remove(key);
    }
    
    public void clear()
    {
        _context.get().clear();
    }
    
    protected void contextRemove()
    {
        _context.remove();
    }
}

Кстати, не могу понять, насколько правильно делать getContext() и releaseContext() synchronized?