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

四大引用

问题切入点

在学习 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

 

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

注意看!!

 

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

 

typescript

代码解读

复制代码

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,我的座右铭是

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值