Java引用类型分析

概述

java.lang.ref 类库包含一组类,为垃圾回收提供了更大的灵活性。

java.lang.ref 有三个继承自抽象类 Reference 的类:

Reference类图

这三个类为垃圾回收器(GC)提供了不同级别的提示,使得GC以不同的策略回收对象。

StrongReference

强引用是使用最普遍的引用,它是默认的引用类型,不需要显式声明,在java.lang.ref中没有实际的类对应,可以把它理解为Java的内置省略默认引用类型。

具有强引用的对象, 只要对象是“可获得的”(reachable),GC就承诺不会回收对象,即使JVM内存不足,抛出OutOfMemoryError异常。

对象是“可获得的”(reachable),是指此对象可在程序中的某处找到。这意味着你在内存栈中有一个普通的引用,而它正指向此对象;也可能是你的引用指向某个对象,而那个对象含有另一个引用,指向正在讨论的对象;也可能有更多的中间链接。

@Test
public void strongReferenceTest() {
    Object obj = new Object();
    System.gc();
    assertThat("obj没被回收", obj, not(nullValue()));
}

SoftReference

只具有软引用的对象,GC承诺在JVM内存充足的时候不回收对象。

@Test
public void softReferenceTest() {
    SoftReference<Object> objSoftReference = new SoftReference<Object>(new Object());
    
    int index = 0;
    long[][] vars = new long[1024][];
    
    long maxMemory;
    long freeMemory;
    
    while(objSoftReference.get() != null) {
        maxMemory = Runtime.getRuntime().maxMemory(); //最大可用内存
        freeMemory = Runtime.getRuntime().freeMemory(); //当前JVM空闲内存
        System.out.printf("maxMemory = %s, freeMemory = %s\n", maxMemory, freeMemory);
    
        vars[index++] = new long[1024];
        System.gc();
    }
    assertThat("obj被回收了", objSoftReference.get(), nullValue());
}

执行上面的用例,刚开始objSoftReference引用的对象不会被GC回收,随着内存逐渐被吃掉,JVM开始觉得内存匮乏了才回收objSoftReference引用的对象。

由此可见,SoftReference在内存充足的时候保持对象,在内存匮乏的时候释放对象。这种回收策略适合应用在内存敏感的高速缓存的场景。

注意: 执行用例前需要设置JVM参数: -Xmx1m,限制jvm的Java Heap最大值。

设置其他的值该用例可能执行失败,原因是:

  1. new long[1024]可能越过了JVM内存不充足的判断边界。
  2. System.gc()调用频率的限制。

WeakReference

只具有弱引用的对象,GC执行时会马上回收对象。

@Test
public void WeakReferenceTest() throws InterruptedException {
    ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
    WeakReference<Object> objWeakReference = new WeakReference<Object>(new Object(), referenceQueue);

    assertThat("还没有执行GC, obj还没被回收", objWeakReference.get(), not(nullValue()));
    assertThat("还没有执行GC, referenceQueue为空", referenceQueue.poll(), nullValue());

    System.gc();
    Thread.sleep(500);  // 确保GC执行完成

    assertThat("执行GC后, obj马上被回收了", objWeakReference.get(), nullValue());
    assertThat("执行GC后, objWeakReference被放入referenceQueue", objWeakReference, equalTo((Reference)referenceQueue.poll()));
}

由于GC线程的优先级比较低,不一定会很快执行GC,所以只具有弱引用的对象可能会继续存活一段时间,这段时间内可以通过get()方法继续获得引用的对象。当GC回收对象后会把objWeakReference放入referenceQueue队列中。

PhantomReference

只具有虚引用的对象,和 没有任何引用一样 ,无论它是否被回收,你永远也取不到引用的对象了,并且GC执行时会马上回收对象。

@Test
public void PhantomReferenceTest() throws InterruptedException {
    ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
    PhantomReference<Object> objPhantomReference = new PhantomReference<Object>(new Object(), referenceQueue);

    assertThat("无法通过虚引用获取到对象", objPhantomReference.get(), nullValue());
    assertThat("还没有执行GC, referenceQueue为空", referenceQueue.poll(), nullValue());

    System.gc();
    Thread.sleep(500);  // 确保GC执行完成

    assertThat("无法通过虚引用获取到对象", objPhantomReference.get(), nullValue());
    assertThat("执行GC后, objPhantomReference被放入referenceQueue", objPhantomReference, equalTo((Reference)referenceQueue.poll()));
}

换言之,当一个只具有虚引用的对象,你已经失去了对它的所有控制权。唯一你可知的是: 对象是否被GC回收了,当GC回收对象后和WeakReference一样,GC会把objPhantomReference放入referenceQueue队列中。

WeakReference vs PhantomReference

目前为止,我们已经可以总结出WeakReferencePhantomReference的一些相同点和不同点。

相同点:

  • 当GC执行时,两者引用的对象都会被回收。
  • 对象被回收后,引用对象本身都会被放入一个ReferenceQueue队列中。

不同点:

  • GC回收引用的对象前,WeakReference还有机会获得引用的对象,而PhantomReference永远失去了和引用的对象之间的联系。
  • 使用SoftReferenceWeakReference时,你可以选择是否要将它们放入ReferenceQueue中。而PhantomReference只能依赖于ReferenceQueue,否则毫无用处。

除了以上的不同点外,WeakReferencePhantomReference之间还有一个最大的不同点,先看用例:

Object obj = null;

@Test
public void WeakReferenceWhenFinalizeTest() throws InterruptedException {

    ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
    WeakReference<Object> objWeakReference = new WeakReference<Object>(
        new Object() {
            public void finalize() {
                obj = this;
            }
        }, referenceQueue);

    assertThat("还没有执行GC, obj还没被回收", objWeakReference.get(), not(nullValue()));
    assertThat("还没有执行GC, referenceQueue为空", referenceQueue.poll(), nullValue());

    System.gc();
    Thread.sleep(500);  // 确保GC执行完成

    assertThat("执行GC后, obj没有被回收,但是无法获取到对象", objWeakReference.get(), nullValue());
    assertThat("执行GC后, obj没有被回收,objWeakReference被放入referenceQueue", objWeakReference, equalTo((Reference)referenceQueue.poll()));
}
Object obj = null;

@Test
public void PhantomReferenceWhenFinalizeTest() throws InterruptedException {
    ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
    PhantomReference<Object> objPhantomReference = new PhantomReference<Object>(
        new Object() {
            public void finalize() {
                obj = this;
            }
        }, referenceQueue);

    assertThat("无法通过虚引用获取到对象", objPhantomReference.get(), nullValue());
    assertThat("还没有执行GC, obj没有被回收,referenceQueue为空", referenceQueue.poll(), nullValue());

    System.gc();
    Thread.sleep(500);  // 确保GC执行完成

    assertThat("无法通过虚引用获取到对象", objPhantomReference.get(), nullValue());
    assertThat("执行GC后, referenceQueue为空", referenceQueue.poll(), nullValue());
}

GC执行时,引用的对象通过finalize()再次将自己激活,GC最终并没有释放引用的对象。

这时:

  • WeakReference已经无法获得引用的对象,并且WeakReference对象被放入了ReferenceQueue
  • PhantomReference对象并没有被放入ReferenceQueue

所以,PhantomReference区别于WeakReference最大的不同是PhantomReference对象只有在对象真正被回收后才会被放入ReferenceQueue

总结

如果你想继续持有对某个对象的引用,希望以后还能够访问到该对象,同时也允许垃圾回收器释放它,这时就应该使用Reference对象。

StrongReferenceSoftReferenceWeakReferencePhantomReference由强到弱排列,应用的场景也各不相同。

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

推荐阅读更多精彩内容