关于ThreadLocal内存泄漏的备忘录

电脑杂谈  发布时间:2020-06-09 21:22:03  来源:网络整理

threadlocal内存泄露_threadlocal static_threadlocal 内存泄露

请记住,您第一次接触ThreadLocal可能会导致内存泄漏. 在接受采访时,您被问到ThreadLocal的缺点是什么. 当然,由于没有与面试官联系,我很遗憾未能确认所谓的缺陷是否可能导致内存泄漏. 但是,后来我发现,尽管当时我很了解,但过了一段时间我忘记忘记了这个问题,因此我专门记录了下来. 成为备忘录.

ThreadLocal从名称很容易理解. 它是用于线程专用(本地)的存储结构. 这种结构使线程可以使用只有它们才能访问和修改的变量,从而实现多个线程. 资源彼此隔离,以达到安全并发的目的.

因此,ThreadLocal作为线程并发中的一种资源使用方法,已被广泛使用,例如Spring MVC,Hibernate等.

但是,值得一提的是,人们通常谈论将ThreadLocal和同步在一起作为形成安全并发的手段之一. 实际上,我认为这更具误导性,因为两者的目的完全不同.

ThreadLocal主要用于独占使用自己的变量,以避免竞争某些资源,从而实现了时间空间的思想.

threadlocal内存泄露_threadlocal static_threadlocal 内存泄露

Synchronized主要用于分配关键(冲突)资源,该资源可以实现线程之间的信息同步,共享公共资源等. 因此严格来说,synchronized实际上能够实现ThreadLocal的预期效果,但是将带来资源竞争并导致并发性能下降,并且可能会有一些不必要的开销,例如同步,线程切换.

对于ThreadLocal,实际上感觉有点像拳击类型的基本类型(个人认为它实际上可以看作是一种装饰器模式吗?),具体用法并不费劲. 让我们看一下本备忘录的重点,如何导致内存泄漏.

事实上threadlocal内存泄露,互联网上的一些文章听得很清楚,我认为这里有一张图片特别值得一提,来自ThreadLocal的图片可能会导致内存泄漏:

[由ThreadLocal引起的可能的内存泄漏]()

因此,简单地说,主要原因是在TreadLocal中使用的自定义Map(不同于常用的Map接口)中,使用的Key值是WeakReference类型值(弱引用将立即在下一个GC版本(无论是否引用). 然后,如果在GC期间释放此键,它将导致永不调用Value,但是如果线程没有结束,则它将始终存在.

threadlocal内存泄露_threadlocal static_threadlocal 内存泄露

由于可能不熟悉这部分内容的学生(例如几周后我)会感到有些困惑,为什么我要详细说明以下内容:

public class ThreadLocal<T> {
    
    //  获取Thread里面的Map
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    // (敲黑板)
    // 这里是重点!!!
    static class ThreadLocalMap {
        
        // 这里是凶器!!!
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        ... 
    }
    ... 
 }

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. 
     */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

所以这里实际上有两个我还没有解决的问题,希望大家能给您一些建议:

实际上可以将TreadLocalMap提取到一个单独的类中吗?这使得逻辑和嵌套关系的意义减少了. 为什么只有键应被设计为WeakReference而不是键和值,或者为什么我们应该在此处设置弱引用?如果为了保护内存空间,两者实际上都是弱引用,是否还有其他考虑因素?

threadlocal内存泄露_threadlocal 内存泄露_threadlocal static

内存泄漏的返回是由于WeakReference Key问题引起的. 当然,Java中的每个人都早就想到了这个问题. 您可以在其他人的评论中看到这一点. 要点是,如果key == null,则可以认为该值无效,可以调用expunged进行清理:

/**
  * The entries in this hash map extend WeakReference, using
  * its main ref field as the key (which is always a
  * ThreadLocal object).  Note that null keys (i.e. entry.get()
  * == null) mean that the key is no longer referenced, so the
  * entry can be expunged from table.  Such entries are referred to
  * as "stale entries" in the code that follows.
  */

这个expungeStaleEntry方法将在get和set期间间接调用,并且该调用也将显示在remove方法中,这就是为什么有些文章说程调用完成之后,可以对remove方法的调用是有效杜绝泄漏的原因.

当然,只需了解一下,基本上就可以理解内存泄漏的原因,但实际上,如果泄漏的原因是释放了键而没有释放值,则实际上要更深入一些,那一定有泄漏吗?

当然答案是否定的,因为在一般线程场景中,除了调用expungeStaleEntry进行清理之外,最糟糕的是threadlocal内存泄露,当线程结束时,自然会删除该引用,以便Value可以由GC回收.

threadlocal static_threadlocal 内存泄露_threadlocal内存泄露

那么,会不会有线程永远不会结束的场景?

当然,答案是肯定的. 用最简单的术语来说,线程只要一直在等待就不会结束,但是在这种情况下,它实际上与泄漏无关.

实际上,线程永远不会结束的最常见情况自然是线程池. 因为在这种情况下,线程会不断重复运行,这可能导致值的累积. 具体的模拟可以参考: 深入了解ThreadLocal的“ ThreadOverflow”

最后,让我们做一个总结. 可能的泄漏只存在于:

线程运行方法结束后,没有显式调用remove来清理线程. 程池模式下,它已经反复运行


本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-239847-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      热点图片
      拼命载入中...