资讯专栏INFORMATION COLUMN

Java引用类型分析

wyk1184 / 2184人阅读

摘要:强引用是使用最普遍的引用,它是默认的引用类型,不需要显式声明,在中没有实际的类对应,可以把它理解为的内置省略默认引用类型。相同点当执行时,两者引用的对象都会被回收。这时已经无法获得引用的对象,并且对象被放入了。

概述

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

java.lang.ref 有三个继承自抽象类 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 objSoftReference = new SoftReference(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
", maxMemory, freeMemory);
    
        vars[index++] = new long[1024];
        System.gc();
    }
    assertThat("obj被回收了", objSoftReference.get(), nullValue());
}

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

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

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

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

new long[1024]可能越过了JVM内存不充足的判断边界。

System.gc()调用频率的限制。

WeakReference

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

@Test
public void WeakReferenceTest() throws InterruptedException {
    ReferenceQueue referenceQueue = new ReferenceQueue();
    WeakReference objWeakReference = new WeakReference(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 referenceQueue = new ReferenceQueue();
    PhantomReference objPhantomReference = new PhantomReference(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 referenceQueue = new ReferenceQueue();
    WeakReference objWeakReference = new WeakReference(
        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 referenceQueue = new ReferenceQueue();
    PhantomReference objPhantomReference = new PhantomReference(
        new Object() {
            public void finalize() {
                obj = this;
            }
        }, referenceQueue);

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

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

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

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

这时:

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

PhantomReference对象并没有被放入ReferenceQueue

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

总结

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

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

Softreference: 只在内存不足时才被回收,主要用以实现内存敏感的高速缓存。

WeakReference: 主要用以实现 规范映射 ,具体的实践可以查看WeakHashMap的实现。

Phantomreference: 可以追踪对象的回收事件,主要用以执行回收前的清理工作,它比finalize()更灵活。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66902.html

相关文章

  • Java向上转型及内存分析

    摘要:但有时候,当我们的代码只需要与父类打交道时,可以使用向上转型,来使我们的代码不依赖具体子类,比如以下代码,方法可以接受类的任意子类内存分析我们来分析以下转型代码在内存中的表示 学习设计模式的时候,发现很多模式都用到了向上转型(eg. 工厂方法)。而我对向上转型(upcasting)的机制并不十分熟悉。这篇文章将深入分析向上转型的机制、内存分析。 概念 先从几个基本概念开始: 1. Ja...

    Zachary 评论0 收藏0
  • 读书笔记之深入理解Java虚拟机

    摘要:前言本文内容基本摘抄自深入理解虚拟机,以供复习之用,没有多少参考价值。此区域是唯一一个在虚拟机规范中没有规定任何情况的区域。堆是所有线程共享的内存区域,在虚拟机启动时创建。虚拟机上把方法区称为永久代。 前言 本文内容基本摘抄自《深入理解Java虚拟机》,以供复习之用,没有多少参考价值。想要更详细了解请参考原书。 第二章 1.运行时数据区域 showImg(https://segment...

    jaysun 评论0 收藏0
  • Java知识点总结(内存分析

    摘要:知识点总结内存分析知识点总结面向对象对象内存类是创建对象的模板对象的使用时通过引用进行的基本数据类型直接赋值,引用类型传递的是一个地址栈存放局部变量堆存放出来的对象方法区存放类的信息代码变量常量池字符串常量等张三王武李四垃圾回收机制对象空间 Java知识点总结(内存分析) @(Java知识点总结)[Java, Java面向对象] [toc] 对象内存 类是创建对象的模板 Java对象的...

    lvzishen 评论0 收藏0
  • JVM类加载思维导图

    摘要:用一张思维导图尽可能囊括一下的类加载过程的全流程。本文参考自来自周志明深入理解虚拟机第版,拓展内容建议读者可以阅读下这本书。 用一张思维导图尽可能囊括一下JVM的类加载过程的全流程。 本文参考自来自周志明《深入理解Java虚拟机(第2版)》,拓展内容建议读者可以阅读下这本书。 showImg(http://ocxhn1mzz.bkt.clouddn.com/class%20loadin...

    Crazy_Coder 评论0 收藏0
  • Java 虚拟机总结给面试的你(中)

    摘要:验证过程验证过程的目的是为了确保文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。二虚拟机字节码执行引擎虚拟机的执行引擎自行实现,可以自行制定指令集与执行引擎的结构体系。 本篇博客主要针对Java虚拟机的类加载机制,虚拟机字节码执行引擎,早期编译优化进行总结,其余部分总结请点击Java虚拟总结上篇 。 一.虚拟机类加载机制 概述 虚拟机把描述类的数据从Clas...

    MRZYD 评论0 收藏0

发表评论

0条评论

wyk1184

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<