1. 前言

ThreadLocal 也是一个使用频率较高的类,在框架中也经常见到,比如 Spring。

有关 ThreadLocal 源码分析的文章不少,其中有个问题常被提及:ThreadLocal 是否存在内存泄漏?

不少文章对此讲述比较模糊,经常让人看完脑子还是一头雾水,我也有此困惑。因此找时间跟小伙伴讨论了一番,总算对这个问题有了一定的理解,这里记录和分享一下,希望对有同样困惑的朋友们有所帮助。当然,若有理解不当的地方也欢迎指正。

啰嗦就到这里,下面先从 ThreadLocal 的一个应用场景开始分析吧。

2. 应用场景

ThreadLocal 的应用场景不少,这里举个简单的栗子:单点登录拦截。

也就是在处理一个 HTTP 请求之前,判断用户是否登录:

  • 若未登录,跳转到登录页面;
  • 若已登录,获取并保存用户的登录信息。

先定义一个 UserInfoHolder 类保存用户的登录信息,其内部用 ThreadLocal 存储,示例如下:

public class UserInfoHolder {    private static final ThreadLocal> USER_INFO_THREAD_LOCAL = new ThreadLocal<>();public static void set(Map map) {        USER_INFO_THREAD_LOCAL.set(map);    }public static Map get() {return USER_INFO_THREAD_LOCAL.get();    }public static void clear() {        USER_INFO_THREAD_LOCAL.remove();    }// ...}

通过 UserInfoHolder 可以存储和获取用户的登录信息,以便在业务中使用。

Spring 项目中,如果我们想在处理一个 HTTP 请求之前或之后做些额外的处理,通常定义一个类继承 HandlerInterceptorAdapter,然后重写它的一些方法。举例如下(仅供参考,省略了一些代码):

public class LoginInterceptor extends HandlerInterceptorAdapter {

    // ...

    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {

        // ...

        // 请求执行前,获取用户登录信息并保存        Map userInfoMap = getUserInfo();        UserInfoHolder.set(userInfoMap);return true;    }@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {// 请求执行后,清理掉用户信息        UserInfoHolder.clear();    }}

在本例中,我们在处理一个请求之前获取用户的信息,在处理完请求之后,将用户信息清空。应该有朋友在框架或者自己的项目中见过类似代码。

下面我们深入 ThreadLocal 的内部,来分析这些方法做了些什么,跟内存泄漏又是怎么扯上关系的。

3. 源码剖析

3.1 类签名

先从头开始,也就是类签名:

public class ThreadLocal<T> {}

可见它就是一个普通的类,并没有实现任何接口、也无父类继承。

3.2 构造器

ThreadLocal 只有一个无参构造器:

public ThreadLocal() {}

此外,JDK 1.8 引入了一个使用 lambda 表达式初始化的静态方法 withInitial,如下:

public static  ThreadLocal withInitial(Supplier extends S> supplier) {return new SuppliedThreadLocal<>(supplier);}

该方法也可以初始化一个对象,和构造器也比较接近。

3.3 ThreadLocalMap

3.3.1 主要代码

ThreadLocalMap 是 ThreadLocal 的一个内部嵌套类。

由于 ThreadLocal 的主要操作实际都是通过 ThreadLocalMap 的方法实现的,因此先分析 ThreadLocalMap 的主要代码:

public class ThreadLocal<T> {    // 生成 ThreadLocal 的哈希码,用于计算在 Entry 数组中的位置    private final int threadLocalHashCode = nextHashCode();

    private static final int HASH_INCREMENT = 0x61c88647;

    private static int nextHashCode() {        return nextHashCode.getAndAdd(HASH_INCREMENT);    }

    // ...

    static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal>> {            Object value;            Entry(ThreadLocal> k, Object v) {                super(k);                value = v;            }        }

        // 初始容量,必须是 2 的次幂        private static final int INITIAL_CAPACITY = 16;

        // 存储数据的数组        private Entry[] table;

        // table 中的 Entry 数量        private int size = 0;

        // 扩容的阈值        private int threshold; // Default to 0

        // 设置扩容阈值        private void setThreshold(int len) {            threshold = len * 2 / 3;        }    

        // 第一次添加元素使用的构造器        ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {            table = new Entry[INITIAL_CAPACITY];            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);            table[i] = new Entry(firstKey, firstValue);            size = 1;            setThreshold(INITIAL_CAPACITY);        }

        // ...    }}

ThreadLocalMap 的内部结构其实跟 HashMap 很类似,可以对比前面「JDK源码分析-HashMap(1)」对 HashMap 的分析。

二者都是「键-值对」构成的数组,对哈希冲突的处理方式不同,导致了它们在结构上产生了一些区别:

  1. HashMap 处理哈希冲突使用的「链表法」。也就是当产生冲突时拉出一个链表,而且 JDK 1.8 进一步引入了红黑树进行优化。
  2. ThreadLocalMap 则使用了「开放寻址法」中的「线性探测」。即,当某个位置出现冲突时,从当前位置往后查找,直到找到一个空闲位置。

其它部分大体是类似的。

3.3.2 注意事项

  • 弱引用

有个值得注意的地方是:ThreadLocalMap 的 Entry 继承了 WeakReference 类,也就是弱引用类型。

跟进 Entry 的父类,可以看到 ThreadLocal 最终赋值给了 WeakReference 的父类 Reference 的 referent 属性。即,可以认为 Entry 持有了两个对象的引用:ThreadLocal 类型的「弱引用」和 Object 类型的「强引用」,其中 ThreadLocal 为 key,Object 为 value。如图所示:

ThreadLocal 在某些情况可能产生的「内存泄漏」就跟这个「弱引用」有关,后面再展开分析。

  • 寻址

Entry 的 key 是 ThreadLocal 类型的,它是如何在数组中散列的呢?

ThreadLocal 有个 threadLocalHashCode 变量,每次创建 ThreadLocal 对象时,这个变量都会增加一个固定的值 HASH_INCREMENT,即 0x61c88647,这个数字似乎跟黄金分割、斐波那契数有关,但这不是重点,有兴趣的朋友可以去深入研究下,这里我们知道它的目的就行了。与 HashMap 的 hash 算法的目的近似,就是为了散列的更均匀。

下面分析 ThreadLocal 的主要方法实现。

3.4 主要方法

ThreadLocal 主要有三个方法:set、get 和 remove,下面分别介绍。

3.4.1 set 方法

  • set 方法:新增/更新 Entry
public void set(T value) {    // 获取当前线程    Thread t = Thread.currentThread();    // 从 Thread 中获取 ThreadLocalMap    ThreadLocalMap map = getMap(t);

    if (map != null)        map.set(this, value);    else        createMap(t, value);}

ThreadLocalMap getMap(Thread t) {    return t.threadLocals;}

threadLocals 是 Thread 持有的一个 ThreadLocalMap 引用,默认是 null:

public class Thread implements Runnable {    // 其他代码...    ThreadLocal.ThreadLocalMap threadLocals = null;}
  • 执行流程

若从当前 Thread 拿到的 ThreadLocalMap 为空,表示该属性并未初始化,执行 createMap 初始化:

void createMap(Thread t, T firstValue) {    t.threadLocals = new ThreadLocalMap(this, firstValue);}

若已存在,则调用 ThreadLocalMap 的 set 方法:

private void set(ThreadLocal> key, Object value) {        Entry[] tab = table;    int len = tab.length;    // 1. 计算 key 在数组中的下标 i    int i = key.threadLocalHashCode & (len-1);

    // 1.1 若数组下标为 i 的位置有元素    // 判断 i 位置的 Entry 是否为空;不为空则从 i 开始向后遍历数组    for (Entry e = tab[i];         e != null;         e = tab[i = nextIndex(i, len)]) {        ThreadLocal> k = e.get();

        // 索引为 i 的元素就是要查找的元素,用新值覆盖旧值,到此返回        if (k == key) {            e.value = value;            return;        }

        // 索引为 i 的元素并非要查找的元素,且该位置中 Entry 的 Key 已经是 null        // Key 为 null 表明该 Entry 已经过期了,此时用新值来替换这个位置的过期值        if (k == null) {            // 替换过期的 Entry,            replaceStaleEntry(key, value, i);            return;        }    }

    // 1.2 若数组下标为 i 的位置为空,将要存储的元素放到 i 的位置    tab[i] = new Entry(key, value);    int sz = ++size;    // 若未清理过期的 Entry,且数组的大小达到阈值,执行 rehash 操作    if (!cleanSomeSlots(i, sz) && sz >= threshold)        rehash();}

先总结下 set 方法主要流程:

首先根据 key 的 threadLocalHashCode 计算它的数组下标:

  1. 如果数组下标的 Entry 不为空,表示该位置已经有元素。由于可能存在哈希冲突,因此这个位置的元素可能并不是要找的元素,所以遍历数组去比较

    1. 如果找到等于当前 key 的 Entry,则用新值替换旧值,返回。
    2. 如果遍历过程中,遇到 Entry 不为空、但是 Entry 的 key 为空的情况,则会做一些清理工作。
  2. 如果数组下标的 Entry 为空,直接将元素放到这里,必要时进行扩容。
  • replaceStaleEntry:替换过期的值,并清理一些过期的 Entry
private void replaceStaleEntry(ThreadLocal> key, Object value,int staleSlot) {    Entry[] tab = table;    int len = tab.length;    Entry e;

    // 从 staleSlot 开始向前遍历,若遇到过期的槽(Entry 的 key 为空),更新 slotToExpunge    // 直到 Entry 为空停止遍历    int slotToExpunge = staleSlot;    for (int i = prevIndex(staleSlot, len);         (e = tab[i]) != null;         i = prevIndex(i, len))        if (e.get() == null)            slotToExpunge = i;

    // 从 staleSlot 开始向后遍历,若遇到与当前 key 相等的 Entry,更新旧值,并将二者换位置    // 目的是把它放到「应该」在的位置    for (int i = nextIndex(staleSlot, len);         (e = tab[i]) != null;         i = nextIndex(i, len)) {        ThreadLocal> k = e.get();

        if (k == key) {            // 更新旧值            e.value = value;

            // 换位置            tab[i] = tab[staleSlot];            tab[staleSlot] = e;

            // Start expunge at preceding stale entry if it exists            if (slotToExpunge == staleSlot)                slotToExpunge = i;            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);            return;        }

        if (k == null && slotToExpunge == staleSlot)            slotToExpunge = i;    }

    // If key not found, put new entry in stale slot    // 若未找到 key,说明 Entry 此前并不存在,新增    tab[staleSlot].value = null;    tab[staleSlot] = new Entry(key, value);

    // If there are any other stale entries in run, expunge them    if (slotToExpunge != staleSlot)        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}

replaceStaleEntry 的主要执行流程如下:

  1. 从 staleSlot 向前遍历数组,直到 Entry 为空时停止遍历。这一步的主要目的是查找 staleSlot 前面过期的 Entry 的数组下标 slotToExpunge。
  2. 从 staleSlot 向后遍历数组
    1. 若 Entry 的 key 与给定的 key 相等,将该 Entry 与 staleSlot 下标的 Entry 互换位置。目的是为了让新增的 Entry 放到它「应该」在的位置。
    2. 若找不到相等的 key,说明该 key 对应的 Entry 不在数组中,将新值放到 staleSlot 位置。该操作其实就是处理哈希冲突的「线性探测」方法:当某个位置已被占用,向后探测下一个位置。
  3. 若 staleSlot 前面存在过期的 Entry,则执行清理操作。

PS: 所谓 Entry「应该」在的位置,就是根据 key 的 threadLocalHashCode 与数组长度取余计算出来的位置,即 k.threadLocalHashCode & (len - 1) ,或者哈希冲突之后的位置,这里只是为了方便描述。

  • expungeStaleEntry:清理过期的 Entry
// staleSlot 表示过期的槽位(即 Entry 数组的下标)private int expungeStaleEntry(int staleSlot) {    Entry[] tab = table;    int len = tab.length;

    // 1. 将给定位置的 Entry 置为 null    tab[staleSlot].value = null;    tab[staleSlot] = null;    size--;

    // Rehash until we encounter null    Entry e;    int i;    // 遍历数组    for (i = nextIndex(staleSlot, len);         (e = tab[i]) != null;         i = nextIndex(i, len)) {        // 获取 Entry 的 key        ThreadLocal> k = e.get();        if (k == null) {            // 若 key 为 null,表示 Entry 过期,将 Entry 置空            e.value = null;            tab[i] = null;            size--;        } else {            // key 不为空,表示 Entry 未过期            // 计算 key 的位置,若 Entry 不在它「应该」在的位置,把它移到「应该」在的位置            int h = k.threadLocalHashCode & (len - 1);            if (h != i) {                tab[i] = null;                // Unlike Knuth 6.4 Algorithm R, we must scan until                // null because multiple entries could have been stale.                while (tab[h] != null)                    h = nextIndex(h, len);                tab[h] = e;            }        }    }    return i;}

该方法主要做了哪些工作呢?

  1. 清空给定位置的 Entry
  2. 从给定位置的下一个开始向后遍历数组
    1. 若遇到 Entry 为 null,结束遍历
    2. 若遇到 key 为空的 Entry(即过期的),就将该 Entry 置空
    3. 若遇到 key 不为空的 Entry,而且经过计算,该 Entry 并不在它「应该」在的位置,则将其移动到它「应该」在的位置
  3. 返回 staleSlot 后面的、Entry 为 null 的索引下标
  • cleanSomeSlots:清理一些槽(Slot)
private boolean cleanSomeSlots(int i, int n) {    boolean removed = false;    Entry[] tab = table;    int len = tab.length;    do {        i = nextIndex(i, len);        Entry e = tab[i];        // Entry 不为空、key 为空,即 Entry 过期        if (e != null && e.get() == null) {            n = len;            removed = true;            // 清理 i 后面连续过期的 Entry,直到 Entry 为 null,返回该 Entry 的下标            i = expungeStaleEntry(i);        }    } while ( (n >>>= 1) != 0);    return removed;}

该方法做了什么呢?从给定位置的下一个开始扫描数组,若遇到 key 为空的 Entry(过期的),则清理该位置及其后面过期的槽。

值得注意的是,该方法循环执行的次数为 log(n)。由于该方法是在 set 方法内部被调用的,也就是新增/更新时:

  1. 如果不扫描和清理,set 方法执行速度很快,但是会存在一些垃圾(过期的 Entry);
  2. 如果每次都扫描清理,不会存在垃圾,但是插入性能会降低到 O(n)。

因此,这个次数其实就一种平衡策略:Entry 数组较小时,就少清理几次;数组较大时,就多清理几次。

  • rehash:调整 Entry 数组
private void rehash() {    // 清理数组中过期的 Entry    expungeStaleEntries();    // Use lower threshold for doubling to avoid hysteresis    if (size >= threshold - threshold / 4)        resize();}

// 从头开始清理整个 Entry 数组private void expungeStaleEntries() {    Entry[] tab = table;    int len = tab.length;    for (int j = 0; j         Entry e = tab[j];        if (e != null && e.get() == null)            expungeStaleEntry(j);    }}

该方法主要作用:

  1. 清理数组中过期的 Entry
  2. 若清理后 Entry 的数量大于等于 threshold 的 3/4,则执行 resize 方法进行扩容
  • resize 方法:Entry 数组扩容
/** * Double the capacity of the table. */private void resize() {    Entry[] oldTab = table;    int oldLen = oldTab.length;    int newLen = oldLen * 2; // 新长度为旧长度的两倍    Entry[] newTab = new Entry[newLen];    int count = 0;

    // 遍历旧的 Entry 数组,将数组中的值移到新数组中    for (int j = 0; j         Entry e = oldTab[j];        if (e != null) {            ThreadLocal> k = e.get();            // 若 Entry 的 key 已过期,则将 Entry 清理掉            if (k == null) {                e.value = null; // Help the GC            } else {                // 计算在新数组中的位置                int h = k.threadLocalHashCode & (newLen - 1);                // 哈希冲突,线性探测下一个位置                while (newTab[h] != null)                    h = nextIndex(h, newLen);                newTab[h] = e;                count++;            }        }    }

    // 设置新的阈值    setThreshold(newLen);    size = count;    table = newTab;}

该方法的作用是 Entry 数组扩容,主要流程:

  1. 创建一个新数组,长度为原数组的 2 倍;
  2. 从下标 0 开始遍历旧数组的所有元素
    1. 若元素已过期(key 为空),则将 value 也置空
    2. 将未过期的元素移到新数组

3.4.2 get 方法

分析完了 set 方法,再看 get 方法就相对容易了不少。

  • get 方法:获取 ThreadLocal 对应的 Entry
public T get() {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null) {        ThreadLocalMap.Entry e = map.getEntry(this);        if (e != null) {            @SuppressWarnings("unchecked")            T result = (T)e.value;            return result;        }    }    return setInitialValue();}

get 方法首先获取当前线程的 ThreadLocalMap 并判断:

  1. 若 Map 已存在,从 Map 中取值
  2. 若 Map 不存在,或者 Map 中获取的值为空,执行 setInitialValue 方法
  • setInitialValue 方法:获取/设置初始值
private T setInitialValue() {    // 获取初始值    T value = initialValue();    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null)        map.set(this, value);    else        createMap(t, value);    return value;}

protected T initialValue() {    return null;}

先取初始值,这个初始值默认为空(该方法是 protected,可以由子类初始化)。

  1. 若 Thread 的 ThreadLocalMap 已初始化,则将初始值存入 Map
  2. 否则,创建 ThreadLocalMap
  3. 返回初始值

除了初始值,其他逻辑跟 set 方法是一样的,这里不再赘述。

PS: 可以看到初始值是惰性初始化的。

  • getEntry:从 Entry 数组中获取给定 key 对应的 Entry
private Entry getEntry(ThreadLocal> key) {    // 计算下标    int i = key.threadLocalHashCode & (table.length - 1);    Entry e = table[i];    // 查找命中    if (e != null && e.get() == key)        return e;    else        return getEntryAfterMiss(key, i, e);}

// key 未命中private Entry getEntryAfterMiss(ThreadLocal> key, int i, Entry e) {    Entry[] tab = table;    int len = tab.length;

    // 遍历数组    while (e != null) {        ThreadLocal> k = e.get();        if (k == key)            return e; // 是要找的 key,返回        if (k == null)            expungeStaleEntry(i); // Entry 已过期,清理 Entry        else            i = nextIndex(i, len); // 向后遍历        e = tab[i];    }    return null;}

3.4.3 remove 方法

  • remove 方法:移除 ThreadLocal 对应的 Entry
public void remove() {    ThreadLocalMap m = getMap(Thread.currentThread());    if (m != null)        m.remove(this);}

这里调用了 ThreadLocalMap 的 remove 方法:

private void remove(ThreadLocal> key) {    Entry[] tab = table;    int len = tab.length;    int i = key.threadLocalHashCode & (len-1);    for (Entry e = tab[i];         e != null;         e = tab[i = nextIndex(i, len)]) {        if (e.get() == key) {            e.clear();            expungeStaleEntry(i);            return;        }    }}

其中 e.clear 调用的是 Entry 的父类 Reference 的 clear 方法:

public void clear() {    this.referent = null;}

其实就是将 Entry 的 key 置空。

remove 方法的主要执行流程如下:

  1. 获取当前线程的 ThreadLocalMap
  2. 以当前 ThreadLocal 做为 key,从 Map 中查找相应的 Entry,将 Entry 的 key 置空
  3. 将该 ThreadLocal 对应的 Entry 置空,并向后遍历清理 Entry 数组,也就是 expungeStaleEntry 方法的操作,前面已经分析过了,这里不再赘述。

3.4.4 主要方法小结

ThreadLocal 的主要方法 set、get 和 remove 前面已经分析过,这里简单做个小结。

set 方法

  • 以当前 ThreadLocal 为 key、新增的 Object 为 value 组成一个 Entry,放入 ThreadLocalMap,也就是 Entry 数组中。
  • 计算 Entry 的位置后
    • 若该槽为空,直接放到这里;并清理一些过期的 Entry,必要时进行扩容。
    • 当遇到散列冲突时,线性探测向后查找数组中为空的、或者已经过期的槽,用新值替换。

get 方法

  • 以当前 ThreadLocal 为 key,从 Entry 数组中查找对应 Entry 的 value

    • 若 ThreadLocalMap 未初始化,则用给定初始值将其初始化
    • 若 ThreadLocalMap 已初始化,从 Entry 数组查找 key

remove 方法:以当前 ThreadLocal 为 key,从 Entry 数组清理掉对应的 Entry,并且再清理该位置后面的、过期的 Entry

方法虽少,但是稍微有点绕,除了做本身的功能,都执行了一些额外的清理操作。

分析了这几个方法的源码之后,下面就来研究一下内存泄漏的问题。

4. 内存泄漏分析

首先说明一点,ThreadLocal 通常作为成员变量或静态变量来使用(也就是共享的),比如前面应用场景中的例子。因为局部变量已经在同一条线程内部了,没必要使用 ThreadLocal。

为便于理解,这里先给出了 Thread、ThreadLocal、ThreadLocalMap、Entry 这几个类在 JVM 的内存示意图:

简单说明:

  • 当一个线程运行时,栈中存在当前 Thread 的栈帧,它持有 ThreadLocalMap 的强引用。

  • ThreadLocal 所在的类持有一个 ThreadLocal 的强引用;同时,ThreadLocalMap 中的 Entry 持有一个 ThreadLocal 的弱引用。

4.1 场景一

若方法执行完毕、线程正常消亡,则 Thread 的 ThreadLocalMap 引用将断开,如图:

以后 GC 发生时,弱引用也会断开,整个 ThreadLocalMap 都会被回收掉,不存在内存泄漏。

4.2 场景二

如果是线程池中的线程呢?也就是线程一直存活。经过 GC 后 Entry 持有的 ThreadLocal 引用断开,Entry 的 key 为空,value 不为空,如图所示:

此时,如果没有任何 remove 或者 get 等清理 Entry 数组的动作,那么该 Entry 的 value 持有的 Object 就不会被回收掉。这样就产生了内存泄漏。

这种情况其实也很容易避免,使用完执行 remove 方法就行了。

5. 小结

本文分析了 ThreadLocal 的主要方法实现,并分析了它可能存在内存泄漏的场景。

  1. ThreadLocal 主要用于当前线程从共享变量中保存一份「副本」,常用的一个场景就是单点登录保存用户的登录信息。
  2. ThreadLocal 将数据存储在 ThreadLocalMap 中,ThreadLocalMap 是由 Entry 构成的数组,结构有点类似 HashMap。
  3. ThreadLocal 使用不当可能会造成内存泄漏。避免内存泄漏的方法是在方法调用结束前执行 ThreadLocal 的 remove 方法。

本文内容就到这里,若有不正之处欢迎指正,觉得有所收获欢迎三连支持~

点个在看,赞?支持我吧

threadlocal使用场景_ThreadLocal有没有内存泄漏?源码给你安排得明明白白相关推荐

  1. 内存池组件以及根据nginx内存池源码设计实现简易内存池

    目录 造轮子内存池原因引入 大量的malloc/free小内存所带来的弊端 弊端 出现场景 大牛解决措施(nginx内存池) 内存池技术 啥叫作内存池技术 内存池技术为啥可以解决上文弊端 高并发内存池 ...

  2. mysql源码分析——InnoDB的内存结构源码

    一.说明 本来是想在前面的一篇分析中把源码和内容同时过一遍,可突然发现,那可能是非常大的一章.所以就把源码独立了出来,在此章节中对相关四类内存数据结构进行分析,在代码分析过程中,可以和前面的说明以及早 ...

  3. B站服务端代码泄漏:如何提交takedown,删除泄漏源码的仓库和Fork

    事件回顾 2019年4月22日下午四点左右,有网友在GitHub上发现一个上线不到6小时就获得5000+Star,Fork数一路飙升的Git仓库.从项目的描述来看,此为B站后台工程源码. 2019年4 ...

  4. 深度剖析SGI STL二级空间配置器内存池源码

    文章目录 一.SGI STL二级空间配置器重要成员解读 二. 二级空间配置器内存池的结构 三. 两个重要的函数 1. _S_round_up 2. _S_freelist_index 四. 内存池al ...

  5. bytebuf池_Netty篇:ByteBuf之内存池源码分析

    Netty的内存池的实现有些复杂,使用了大量的位运算,晦涩难懂,不过万能的博客上好多大神已经介绍的非常详细,推荐四篇很详细很棒的源码分析的文章链接,本文根据自己的理解顺一下思路,内容主要也是出自以下四 ...

  6. Java Semaphore实现高并发场景下的流量控制(附源码) | 实用代码架构

    目录 前言 Semaphore介绍 代码演示 总结 前言 在java开发的工作中是否会出现这样的场景,你需要实现一些异步运行的任务,该任务可能存在消耗大量内存的情况,所以需要对任务进行并发控制.如何优 ...

  7. Android 匿名共享内存驱动源码分析

    原址 Android系统的匿名共享内存Ashmem驱动程序利用了Linux的共享内存子系统导出的接口来实现,本文通过源码分析方式详细介绍Android系统的匿名共享内存机制.在Android系统中,匿 ...

  8. c++ thread 内存泄漏_深入剖析ThreadLocal原理、内存泄漏及应用场景

    本文主要针对JDK1.8讲解 ThreadLocal作用 先看一个简单的示例,创建两个线程,第一个线程向ThreadLocal中写入数据,第二个线程等待第一个线程完成从ThreadLocal中读取数据 ...

  9. ThreadLocal 内存泄漏问题

    本文说说ThreadLocal由于使用不当造成的内存泄漏问题 Thead和ThreadLocal的内存状况如下图,不了解的同学参考: 图解ThreadLocal核心原理 如果线程太多,每个线程的val ...

最新文章

  1. MFC窗体控件随窗体变化
  2. 01迷宫(BFS+记忆)
  3. 关于重写session实现的时候可能会导至nginx 502的问题
  4. python模块离线安装_离线安装db2的python模块ibm_db
  5. 前端学习(1815):前端调试之css flex 练习1
  6. 052、JVM实战总结:从测试到上线:如何分析JVM运行状况及合理优化?
  7. 特征抽取 PCA主成分分析
  8. 百度开源移动端深度学习框架mobile-deep-learning(MDL)
  9. 实际应用中installshield的事件处理
  10. linux夸分区建立软链接,Linux硬链接和软链接
  11. sync Command in Unix
  12. ThreadLocal的作用
  13. iPad被停用,安装iTunes提示安装包出错解决办法
  14. Oracle执行计划
  15. 如何在2015年后的MacBook Air上安装双系统
  16. 关于前端部分页面模板化构思及可配置的json模板
  17. Python学习之文件13
  18. Wireshark 301: Finding the busiest computers on your network
  19. 什么是 CSRF 攻击?如何防范 CSRF 攻击?
  20. python爬虫文件下载很慢卡住线程_python爬虫多线程假死怎么解决?

热门文章

  1. linux内核串口调试,linux 串口调试方法
  2. c语言创建json串,Jquery通过JSON字符串创建JSON对象
  3. python lambda函数两个列表大小关系_python lambda结合列表推导式?
  4. Python基础——import(导入模块)
  5. Learn OpenGL(一)图形渲染管线(Pipeline)
  6. Redis教程:基础知识
  7. Linux 下 Tomcat Https
  8. SQL SERVER中的二种获得自增长ID的方法
  9. 实现类似add(1)(2)(3)的效果
  10. [iOS]UIDynamicAnimator动画