开场白
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