java四种引用方式与ThreadLocal解析

java四种引用类型

从jdk1.2以来,java把对象的引用定义为四种级别,从而使程序可以更加灵活地控制对象的生命周期。四种引用类型按照由强到弱的顺序分别为:强引用、软引用、弱引用、虚引用。

强引用

public static void main(String[] args) throws IOException {
    M m = new M();
    m = null;
    System.gc();
    System.in.read();
}

m就是持有堆内M对象的一个强引用,也是我们最常见的一种引用形式,属于不可回收资源,垃圾回收期不会主动回收它。当内存空间不足的时候,jvm会抛出OutOfMemoryError错误,也不会主动释放强引用对象。

如果想释放强引用对象,可以显示的把引用复制为null,这样jvm就会在合适的时间释放掉强引用对象。

软引用

SoftReference<byte[]> m = new SoftReference(new byte[1024 * 1024 * 10]);
System.out.println(m.get());
System.gc();
try {
    Thread.sleep(500);
} catch (InterruptedException e) {
    e.printStackTrace();
}
byte[] b = new byte[1024 * 1024 * 15];
System.out.println(m.get() );

如果内存空间足够,垃圾回收器不会回收软引用对象,如果内存空间不足,垃圾回收器一定会回收软引用的对象。

弱引用

public static void main(String[] args) {
    WeakReference<M> m = new WeakReference<>(new M());
    System.out.println(m.get());  // 返回对象
    System.gc();
    System.out.println(m.get());  // 返回null
}

弱引用有更短的生命周期,垃圾回收器只要扫描到弱引用对象,就会清除弱引用对象。

虚引用

private static final List<Object> LIST = new LinkedList<>();
private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();

public static void main(String[] args) {
    PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);
    System.out.println(phantomReference.get());

    new Thread(()->{
        for (int i = 0; i < 30; i++) {
            LIST.add(new byte[1024 * 1024]);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            }
            System.out.println(phantomReference.get());
        }

    }).start();

    new Thread(()->{
        while (true) {
            Reference<? extends M> poll = QUEUE.poll();
            if (poll != null) {
                System.out.println("虚引用对象被jvm回收了"+poll);
            }
        }
    }).start();

    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

虚引用不影响对象的生命周期。虚引用需要和引用队列相关,当垃圾回收器准备回收一个对象,发现它还有虚引用,就会把虚引用加入到与之关联的引用队列中。

ThreadLocal与弱引用

ThreadLocal是什么

ThreadLocal是java.lang包中实现相同线程数据共享、不同线程数据隔离的工具。

static final ThreadLocal<String> tl = new ThreadLocal<>();
public static void main(String[] args) {
    new Thread(()->{
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+tl.get());
    }).start();

    new Thread(()->{
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tl.set("hello");
    }).start();
    
}

ThreadLocal和锁之间的关系:锁解决的是线程之间数据共享的问题,ThreadLocal出现的场景是线程之间没有数据共享,只涉及到同一个线程不同位置使用数据的问题。

ThreadLocal实现原理

对象实例(对应value)和ThreadLocal对象实例(对应key)是由线程Thread来维护的。对象实例和ThreadLocal实例的映射关系存放在Thread对象中的一个map属性中,这个map的类型为ThreadLocalMap。

由于ThreadLocalMap没有对外暴露方法,想要修改或者获取map中的值只能通过ThreadLocal的api来完成。如果Threadlocal对象销毁了,那么就没有办法获取到map中的值了。

// ThreadLocal的set方法
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

// 可以看到map是Thread对象的一个属性
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

// 创建map,并赋值给Thread对象的一个属性
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

所以涉及的值存储的地方都是由ThreadLocalMap来完成的,ThreadLocalMap维护了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;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

ThreadLocalMap中使用Entry[]数组来维护键值对的映射关系,其中key为ThreadLocal对象,value为实例对象value。并且这里的key使用了弱引用,如果对应的ThreadLocal对象的强引用不存在了,这里面的ThreadLocal对象也会被清理掉。

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

这样设计的目的主要是:如果ThreadLocal对象已经不在被强引用了,那么已经没有办法再访问到map中的值了,因为通过Thread对象的属性访问map,然后map再访问value的路径走不通(map的方法不对外暴露)。这个时候可以认为key已经没有用了。

那么为什么不把value也设置为弱引用呢?因为不清楚这个value除了map的引用是否还存在其他的引用。如果是弱引用,当其他地方value的强引用被清理掉之后,gc时就把value(只有弱引用)直接干掉了。Threadlocal对象作为的key还存在,而value却不存在了,显然是不合适的。

ThreadLocal与内存泄露

当把ThreadLocal对象的强引用设置为null之后,就只剩下TheadLocalMap的key为弱引用类型,那么在下次gc的时候会回收掉ThreadLocal对象占用的空间,key就变成null了,而value还存在没有被回收。而且由于map是Thread对象的一个属性,只要Thread对象不销毁,那么value的强引用就一直存在,这样就导致了内存泄露。如果Thread对象过一段时间就销毁,那么影响不会特别大,如果是在线程池场景下,Thread对象一直不销毁,就会导致value一直存在。

所以当线程中的某个ThreadLocal对象使用完了,需要立即调用remove方法,删除Entry对象。

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

推荐阅读更多精彩内容