一文弄清Java的四大引用及其两大传递

开场白

Hello大家好呀,我是CodeCodeBond
最近在复习很多很多的基础知识,有了很多新的感悟~


在这里插入图片描述
话不多说,直接发车✈


四大引用

问题切入点

在学习 Thread线程利用ThreadLocalMap实现线程的本地内存(变量副本)的时候,是基于ThreadLocal为Key,Object为Value的一个Entry。并且,这个Entry键值对的键是使用了一个弱引用,且说道这样会存在一个内存泄漏的问题。
在这里插入图片描述
那么为什么会内存泄露呢?这个弱引用是什么呢?又有哪些引用呢?他们之间有什么区别呢?

怀揣着一堆问题,我开始了学习— 🐻

其实引用类型的选择也是垃圾回收、是否可达的策略选择!

所以我们从垃圾回收和可达性这两个方面去理解Java的四种引用类型!

Java有哪四种类型的引用?

可达性分析是JVM中垃圾回收的垃圾判断算法,通过从GC Root出发->遍历引用链判断对象是否可达判断对象是否进行垃圾回收,具体GC Root是什么,垃圾回收算法是什么就不多赘述了。

强引用

定义: 强引用是最常用最常见的引用,我们正常使用没有特别处理的都是强引用的场景。

特点

  • 只要有一个对象是强引用指向它,就不会被垃圾回收。
  • 在可达性分析中,从GC Roots开始的引用链上,只要有强引用链条存在,目标对象就被视为可达。

软引用

定义: 软引用通过SoftReference类来实现,允许垃圾回收器在内存不足时回收这些对象。

特点

  • 软引用对象在内存充足时不会被回收,但在内存不足时可能会被回收。
  • 在可达性分析中,软引用对象不被立即视为可达,只有在内存充足的情况下,软引用对象才被保留。

弱引用

定义:弱引用通过WeakReference类来实现,允许垃圾回收器在下一次垃圾回收时回收这些对象。

影响

  • 弱引用对象在下一次GC时大概率会被回收,不论内存是否充足。
  • 在可达性分析中,弱引用对象不会被视为可达,即使对象有弱引用,仍然会被回收。

虚引用

定义:虚引用通过PhantomReference类来实现,主要用于跟踪对象的回收状态。(像一次性用品)

影响

  • 虚引用对象本身并不会影响对象的生命周期,它们总是在GC时被回收。
  • 在可达性分析中,虚引用对象从未被视为可达。它们主要用于管理系统资源的回收和清理。

回归问题

回到问题的开始,为什么ThreadLocalMap会存在内存泄漏的问题呢?主要原因就是因为身为Key的ThreadLocal是一个弱引用的存在,它在没有外部强引用的前提下,很可能被直接垃圾回收掉,但是Value是一个Object的强引用,导致Key被gc了,但是Value还在。也就是我们说的内存泄露的问题了。

ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()get()remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后最好手动调用remove()方法,预防一下。

两大传递

两大传递一般指的是值传递引用传递

但是,在Java中是不存在引用传递的,Java所有的传递都是值传递

我们一起来简单证明一下:

基本数据类型

引用数据类型 试验1

public class Test {
    public static void main(String[] args) {
        String s1 = "CodeCodeBond";
        modifyString(s1);
        System.out.println(s1);
    }

    public static void modifyString(String s){
        s = "GGBond!";
    }
}
//打印结果不变
//CodeCodeBond

这是一个最简单的String引用类型,可以看到修改后打印结果不变,这个例子可以说明String是值传递的,方法的形参并没有影响原来的实参。

引用数据类型 试验2

注意看!!

public class Test {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        System.out.println("init:" + list);
        modify(list);
        System.out.println("modify later:" + list);
    }

    public static void modify(ArrayList<String> list){
        list.add("CodeCodeBond");
    }
}

在这里插入图片描述
我们可以看到实参集合居然被修改了,不是说是值传递吗?为什么实参被修改了?

原来,Java对于对象引用类型,传递的是引用的副本(内存地址值)。通过这个引用副本可以修改原始对象的内容。

思考题 Question

但是我们看回试验1的String引用类型为什么不能修改实参呢?

可以思考一下这个问题~文末Answer我们再来解答这个问题(点击目录快速跳转)

引用数据类型 试验3

为了进一步证明Java中确实只有值传递,我们来进行试验3

public class Test {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("CodeCodeBond");
        System.out.println("init:" + list);
        modify(list);
        System.out.println("modify later:" + list);
    }

    public static void modify(ArrayList<String> list){
        list = new ArrayList<>();
        list.add("GGBond");
    }
}

打印结果如图:
在这里插入图片描述
看吧,在方法中我们重新将引用副本指向一个新的地址,但是实参并没有被改变,也就强力的说明了传递的是一个引用副本,有力证明了Java中确实只存在值传递这一说!


最后的最后

思考题 Answer

为什么试验1中,String类值传递引用副本值的时候,无法修改原来实参呢?因为在方法中修改的时候是通过直接赋值的操作来修改,而String是一个final class不可变类,所以String机制是新建一个String对象去存储"CodeCodeBond"字符串的,所以并不能修改实参,你想到了吗?😄

我是CodeCodeBond,我的座右铭是

比上次好,比下次差。

我们下次见! 8.3 1:25
在这里插入图片描述

  • 31
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值