ThreadLocalメモリリーク分析とその回避方法

序文

ThreadLocalによるメモリリークを分析する前に、ThreadLocalがメモリリークを引き起こす理由をより詳しく分析するために、メモリリーク、強参照と弱参照、およびGCリサイクルメカニズムを一般化して理解する必要があります。この状況を回避し、システムの堅牢性を向上させる方法を理解することがより重要です。

メモリリーク

メモリリークとは、メモリを適用した後、プログラムが適用されたメモリスペースを解放できないことです。メモリリークの危険性は無視できますが、メモリリークの蓄積は非常に深刻です。メモリの量に関係なく、いずれ占有されます。

広く口語的には、使用されなくなったオブジェクトまたは変数によって占有されていたメモリを回復できないこと、つまりメモリリークが発生することを意味します。

強い参照と弱い参照

強い参照、最も一般的に使用される参照、つまり強い参照を持つオブジェクトは、ガベージコレクターによって収集されません。メモリ空間が不十分な場合、Java仮想マシンはOutOfMemoryErrorをスローし、プログラムを異常終了させ、そのようなオブジェクトをリサイクルしません。

強い参照とオブジェクトの間の関連付けをキャンセルする場合は、参照をnullに明示的に割り当てて、JVMが適切なときにオブジェクトをリサイクルできるようにすることができます。

弱い参照: JVMがガベージコレクションを実行すると、メモリが十分であるかどうかに関係なく、弱い参照に関連付けられているオブジェクトが収集されます。Javaでは、java.lang.ref.WeakReferenceクラスで表されます。弱参照はキャッシュで使用できます。

GCリサイクルメカニズム-リサイクルが必要なオブジェクトを見つける方法

JVMがリサイクルする必要のあるオブジェクトを見つける方法は2つあります。

  • 参照カウント方法:各オブジェクトには参照カウント属性があり、新しい参照が追加されるとカウントが1増加し、参照が解放されるとカウントが1減少し、カウントが0のときにリサイクルできます。

  • 到達可能性分析方法:GCルートから下方向に検索し、検索がたどるパスを参照チェーンと呼びます。オブジェクトが参照チェーンなしでGCルートに接続されている場合、オブジェクトが使用できないことが証明され、仮想マシンはそれがリサイクル可能なオブジェクトであると判断します。

参照カウント方式では、AはBを参照し、Bは今後使用されなくてもAを参照する場合がありますが、相互参照カウンター= 1であるため、再利用できません。

ThreadLocalメモリーリーク分析

最初に序文からいくつかの概念を理解し(無視されました)、次にThreadLocalによって引き起こされるメモリリークの分析を正式に理解し始めます。

実施原則

static class ThreadLocalMap {static class Entry extends WeakReference <ThreadLocal <?>> {/ **このThreadLocalに関連付けられた値。* / 
        オブジェクト値; 

        Entry(ThreadLocal <?> k、Object v){super(k); 
            値= v; 
        } 
    } 
    ... 
   }

ThreadLocalの実装原理、各スレッドはThreadLocalMapを維持し、キーは弱い参照を使用するThreadLocalインスタンスであり、値はスレッド変数のコピーです。これらのオブジェクト間の参照関係は次のとおりです。

実線の矢印は強い参照を示し、白抜きの矢印は弱い参照を示します

ThreadLocalメモリリークの原因

上の図からわかるように、hreadLocalMapはThreadLocalの弱い参照をキーとして使用します。ThreadLocalに外部の強い参照がない場合、Key(ThreadLocal)はGCによってリサイクルされるようにバインドされます。強い参照があり、theadスレッドが終了した後にのみ、値の強い参照チェーンが壊れます。

但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:

Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value

永远无法回收,造成内存泄漏。郑州不孕不育医院哪家好:http://www.zzfkyy120.com/

那为什么使用弱引用而不是强引用??

我们看看Key使用的

key 使用强引用

当hreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

key 使用弱引用

当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。郑州看不孕不育哪家好:http://jbk.39.net/yiyuanfengcai/tsyl_zztjyy/10502/

ThreadLocalMap的remove()分析

在这里只分析remove()方式,其他的方法可以查看源码进行分析:

private void remove(ThreadLocal<?> key) {    //使用hash方式,计算当前ThreadLocal变量所在table数组位置
    Entry[] tab = table;    int len = tab.length;    int i = key.threadLocalHashCode & (len-1);    //再次循环判断是否在为ThreadLocal变量所在table数组位置
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {        if (e.get() == key) {            //调用WeakReference的clear方法清除对ThreadLocal的弱引用
            e.clear();            //清理key为null的元素
            expungeStaleEntry(i);            return;
        }
    }
}

再看看清理key为null的元素expungeStaleEntry(i):

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;    int len = tab.length;    // 根据强引用的取消强引用关联规则,将value显式地设置成null,去除引用
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;    // 重新hash,并对table中key为null进行处理
    Entry e;    int i;    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();        //对table中key为null进行处理,将value设置为null,清除value的引用
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {            int h = k.threadLocalHashCode & (len - 1);            if (h != i) {
                tab[i] = null;                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }    return i;
}

总结

由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。

但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

ThreadLocal正确的使用方法

  • 每次使用完ThreadLocal都调用它的remove()方法清除数据

  • 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。郑州不孕不育医院哪家好:http://www.xbzztj.com/


おすすめ

転載: blog.51cto.com/14510351/2488802