Java四种引用类型与ThreadLocal内存泄露

一、java中四种引用类型

  1. 强引用 NormalReference(一个普通变量指向一个对象,引用消失以后,对象就会被GC)
    Object o = new Object()
  2. 软引用 SoftReference(有一个软引用对象,软引用对象中有个引用指向一个对象,这个对象是被软引用连着的,在GC的时候会被特殊处理,堆内存不够用的时候就会被回收)
/**
 * -Xmx30M 设置最大堆内存为30M
 */
public class SoftReferenceDemo {
    public static void main(String[] args) {
        SoftReference<byte[]> m = new SoftReference<byte[]>(new byte[1024*1024*10]);
        System.out.println(m.get());
        System.gc();
        try {
            Thread.sleep(500);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println(m.get());

        // 再分配一个数组,占据堆内存空间的超过65%=10M,就会为null,小于65%比如9M则为对象地址
       // 如果直接和上面的对象占用堆内存大小加起来大于30M则java.lang.OutOfMemoryError: Java heap space
        byte[] b = new byte[1024*1024*10];
        System.out.println(m.get());
    }
}
  1. 弱引用 WeakReference(有一个弱引用对象,弱引用对象中有个引用指向一个对象,这个对象只要遇到GC就会被回收)弱引用作用是解决内存泄露
public class WeakReferenceDemo {
    public static void main(String[] args) {
        WeakReference<Person> m=new WeakReference<Person>(new Person());

        System.out.println(m.get());
        System.gc();
        System.out.println(m.get());
    }
}
  1. 虚引用 PhantomReference(有跟没有差不多,永远get不到,作用是管理堆外内存)

二、ThreadLocal

threadLocal就是一个容器,A线程只能拿到自己放入threadLocal的东西,拿不到B线程放进去的东西

使用案例

不使用threadLocal

public class ThreadLocalDemo {
    volatile static Person p = new Person();

    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(p.name);
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                p.name="lisi";
            }
        }).start();
    }
}

class Person{
    String name="zhangsan";
}

上述打印结果lisi
使用threadLocal以后

public class ThreadLocalDemo {
//    volatile static Person p = new Person();
    static ThreadLocal<Person> t1= new ThreadLocal<Person>();
    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(t1.get());
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                t1.set(new Person());
            }
        }).start();
    }
}

class Person{
    String name="zhangsan";
}

打印结果为null

源码解析

java.lang.ThreadLocal #set

    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的本地map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

java.lang.ThreadLocal.ThreadLocalMap #set

private void set(ThreadLocal<?> key, Object value) {
            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)]) {
                ThreadLocal<?> k = e.get();
                if (k == key) {
                    e.value = value;
                    return;
                }
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
// new一个虚引用
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

注意:类似于hashmap7的结构是一个entry数组,entry是链表节点,下面也是new一个entry键值对作为threadLocalMap的结构数组中成员

java.lang.ThreadLocal.ThreadLocalMap

static class ThreadLocalMap {
    // 这个Entry是一个weakReference
    static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
// 调用子类的构造方法的时候,会先去实例化父类
// 下面这句等效于 new WeakReference(new K()),即k对象是被弱引用指向的
                super(k);
                value = v;
            }
        }
}

java.lang.ThreadLocal.ThreadLocalMap #getMap

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

获取的是当前线程里面的threadlocalmap,自然B线程无法获取A线程放置的内容,threadLocal最明显的使用时spring中的事务@Transaction

流程:new 一个threadLocal,然后调用set方法,引用了当前线程的threadLocalMap,然后创建一个Entry对象即弱引用对象,让该弱引用对象指向new的threadLocal对象这个key,然后就将这个Entry放到threadLocalMap中

面试题1 为什么Entry要用弱引用

下面代码中的这个threadLocal对象,有2个地方引用它

  1. 一个是强引用 ThreadLocal<Person> t1;
  2. 还有一个是线程的threadLocalMap的一个Entry键值对的key也指向他,并且这个指向是一个弱引用,弱指向
ThreadLocal<Person> t1=new ThreadLocal<>();
r1.set(new Person());
t1.remove();

内存泄露场景1:
假设Entry为强引用,因为是强引用,当我们写t1=null的时候(或者main方法退出),t1不再使用的时候,这个new出来的threadLocal应该被回收掉,可是因为在t1中set了一个new Person(),则ThreadLocalMap中仍然有个entry的key指向这个ThreadLocal对象t1,因此该对象无法回收,如果程序一直运行,则该对象永远无法回收,因为有个强引用永远指向他,造成了内存泄露问题

因此Entry为弱引用,ThreadLocalMap的key弱指向threadLocal对象t1,只要有GC,这个t1就会被回收

内存泄露场景2:
当我们通过弱引用将ThreadLocal对象t1回收以后,就出现了key为null,但是value存在的情况,value则面临无法回收的局面,因为已经无法通过这个null找到这个value,导致越来越多的这种积累,造成内存泄漏

**总结:

因此正常的threadLocal使用方法是确定new出来的Person不再引用以后,使用t1.remove()将整个Entry行从ThreadLocalMap中删除**

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容