前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java4种引用类型到底如何用?

Java4种引用类型到底如何用?

作者头像
chengcheng222e
发布2021-11-04 16:30:45
3150
发布2021-11-04 16:30:45
举报
文章被收录于专栏:简栈文化简栈文化

JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:

  • 强引用(Strong Reference)
  • 软引用(Soft Reference)
  • 弱引用(Weak Reference)
  • 虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱。不同的引用在垃圾回收中体现也是不一样~
M

我们先创建一个M对象,后面为了方便的感受GC的情况。

代码语言:javascript
复制
package com.cyblogs.java.learning.C001_ReferenceType;

/**
 * Created with java-learning-demo
 *
 * @description:
 * @author: chenyuan
 * @date: 2020/6/16
 * @time: 9:30 PM
 */
public class M {

    @Override
    public void finalize() {
        System.out.println("finalize");
    }

}

finalize函数是对象在gc的时候,一定会调用该方法。我们重写一下该方法并且打印一行日志。

强引用

只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了。

代码语言:javascript
复制
package com.cyblogs.java.learning.C001_ReferenceType;

import java.io.IOException;

/**
 * Created with java-learning-demo
 *
 * @description: 正常引用/强引用
 * @author: chenyuan
 * @date: 2020/6/16
 * @time: 9:31 PM
 */
public class C001_01_NormalReference {

    public static void main(String[] args) throws IOException {
        M m = new M();
        // 将对象复制为空
        m = null;
        // 手动触发GC
        System.gc();
        // 因为不是触发gc就一定会立马gc,所以让线程阻塞一下
        System.in.read();
    }
}

控制台日志输出:

代码语言:javascript
复制
Connected to the target VM, address: '127.0.0.1:53621', transport: 'socket'
finalize
Disconnected from the target VM, address: '127.0.0.1:53621', transport: 'socket'
软引用

软引用是用来描述一些非必需但仍有用的对象。「在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常」。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。

代码语言:javascript
复制
package com.cyblogs.java.learning.C001_ReferenceType;

import java.lang.ref.SoftReference;

/**
 * Created with java-learning-demo
 *
 * @description: 软引用:当内存不足的时候,gc才会回收。非常适合做缓存
 * @author: chenyuan
 * @date: 2020/6/16
 * @time: 9:31 PM
 */
public class C001_02_SoftReference {

    public static void main(String[] args) throws InterruptedException {
        // 开辟一个20M的空间
        SoftReference<byte[]> m = new SoftReference<byte[]>(new byte[1024 * 1024 * 10]);
        System.out.println(m.get());
        // 手动GC一下,看是否可以GC掉
        System.gc();
        // 避免gc不会立马触发,尝试休眠1s
        Thread.sleep(1000);
        // 然后再尝试获取
        System.out.println(m.get());
        // 重新开辟一个空间
        byte[] b = new byte[1024 * 1024 * 15];
        System.out.println(m.get());
    }
}

因为它是在内存不足的时候才会触发,所以我们在跑之前需要设置一下最大堆。

代码语言:javascript
复制
-Xmx20M

控制台日志输出:

代码语言:javascript
复制
Connected to the target VM, address: '127.0.0.1:54335', transport: 'socket'
[B@4c3e4790
[B@4c3e4790
null
Disconnected from the target VM, address: '127.0.0.1:54335', transport: 'socket'

你会发现就算我们gc了,后面还是会get得到,因为空间还足够。当后面byte[] b再继续申请空间的时候,发现空间不足了,这个时候就会触发gc动作,把软引用的部分清除掉。

弱引用

弱引用的引用强度比软引用要更弱一些,「无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收」

代码语言:javascript
复制
package com.cyblogs.java.learning.C001_ReferenceType;

import java.lang.ref.WeakReference;

/**
 * Created with java-learning-demo
 *
 * @description: 弱引用:是为了解决某些地方的内存泄露问题
 * @author: chenyuan
 * @date: 2020/6/16
 * @time: 9:32 PM
 */
public class C001_03_WeakReference {
    public static void main(String[] args) {
        WeakReference<M> m = new WeakReference<M>(new M());

        System.out.println(m.get());
        System.gc();
        System.out.println(m.get());

        ThreadLocal<M> tl = new ThreadLocal<M>();
        tl.set(new M());
        tl.remove();
    }
}

控制台日志输出:

代码语言:javascript
复制
Connected to the target VM, address: '127.0.0.1:55151', transport: 'socket'
com.cyblogs.java.learning.C001_ReferenceType.M@38cccef
null
finalize
Disconnected from the target VM, address: '127.0.0.1:55151', transport: 'socket'

我们看一下ThreadLocalset方法:

代码语言:javascript
复制
/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

http://static.cyblogs.com/QQ20200616-225739@2x.jpg

为什么Entry要使用弱引用?

  • 若是强引用,即使tl=null,但是key的引用还是指向ThreadLocal。所以内存会泄露~而弱引用不会
  • 但是还会有内存泄露的问题,ThreadLocal被回收。key的值变成了null,则导致value的值再也无法被访问到,因此依然存在内存泄露问题。
虚引用

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。

代码语言:javascript
复制
package com.cyblogs.java.learning.C001_ReferenceType;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.LinkedList;
import java.util.List;

/**
 * Created with java-learning-demo
 *
 * @description: 虚引用:
 * @author: chenyuan
 * @date: 2020/6/16
 * @time: 9:33 PM
 */
public class C001_04_PhantomReference {

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


    public static void main(String[] args) throws InterruptedException {
        PhantomReference<M> m = new PhantomReference<M>(new M(), QUEUE);

        // 线程1
        new Thread(() -> {
            while (true){
                LIST.add(new byte[1024 * 1024]);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(m.get());
            }
        }).start();

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

        Thread.sleep(500);
    }
}

在零拷贝中就会使用到虚引用,但我们又无法去操作对外的内存。因为太弱了,我们也无法感知到~ 这里就需要利用到ReferenceQueue

http://static.cyblogs.com/QQ20200616-222909@2x.jpg

参考地址
  • https://www.bilibili.com/video/BV1iK4y1x7tj
  • https://www.cnblogs.com/liyutian/p/9690974.html

如果大家喜欢我的文章,可以关注个人订阅号。欢迎随时留言、交流。如果想加入微信群的话一起讨论的话,请加管理员简栈文化-小助手(lastpass4u),他会拉你们进群。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 简栈文化 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • M
  • 强引用
  • 软引用
  • 弱引用
  • 虚引用
  • 参考地址
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档