前言
从初识java的时候,我们就知道java不同于C语言,是没有指针的概念的,有两种类型,基础类型和引用类型,java中一切皆对象......这里的对象其实就是所谓的引用类型,但是很少有人真的去关注引用类型,机缘巧合,今天看到了引用类型的四种模式的讨论资料,那作为一个不称职的底层程序狗,就来总结一下自己的认识。
引用与对象
上面已经说了,java不同于C语言,是通过“引用”来操作内存的,也可以说我们经常在程序中传递的对象参数,其实都是对这个对象的一种引用。
在 JDK1.2 之前,Java中的定义很传统:如果类型的数据中存储的数值代表的是另外一块内存的起始地址,就称为这块内存代表着一个引用。
Java 中的垃圾回收机制在判断是否回收某个对象的时候,都需要依据“引用”这个概念。
在不同垃圾回收算法中,对引用的判断方式有所不同:
- 引用计数法:为每个对象添加一个引用计数器,每当有一个引用指向它时,计数器就加1,当引用失效时,计数器就减1,当计数器为0时,则认为该对象可以被回收(目前在Java中已经弃用这种方式了)。
- 可达性分析算法:从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。
JDK1.2 之前,一个对象只有“已被引用”和"未被引用"两种状态,这将无法描述某些特殊情况下的对象,比如,当内存充足时需要保留,而内存紧张时才需要被抛弃的一类对象。
四种引用类型
在 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱。这个改动的影响有两方面:
- 可以在代码中决定某些对象的生命周期;
- 优化JVM的垃圾回收机制
对于JVM垃圾回收机制是一种极大的补充。
强引用
import java.util.ArrayList;
import java.util.List;
public class StrongReferenceTest {
static class BigObject {
private Byte[] bytes = new Byte[1024 * 1024];
}
public static void main(String[] args) {
List<BigObject> list = new ArrayList<>();
while (true) {
BigObject obj = new BigObject();
list.add(obj);
}
}
}
强引用是最普遍的引用,如果一个对象具有强引用,垃圾回收器不会回收该对象,当内存空间不足时,JVM 宁愿抛出 OutOfMemoryError
异常;只有当这个对象没有被引用时,才有可能会被回收。BigObject obj = new BigObject()
创建的这个obj对象时就是强引用,这段代码最终会抛出内存溢出异常
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
软引用
软引用顾名思义,这个“引用”的动作并不是绝对的
- 当前环境的内存充足的情况下,并不会对软引用对象进行垃圾回收
- 当内存吃紧后,JVM触发内存回收机制,寻找可以释放的软对象,优先回收长时间没有被使用或者操作的“软引用对象”,尽可能保留刚创建的“软引用对象”
- 在JVM进行内存回收后还是内存吃紧,才会抛出内存溢出的异常
软引用在Java中用java.lang.ref.SoftReference类来表示。
将JVM内存设置为8m
代码如下:
import java.lang.ref.SoftReference;
/**
* @Auther: LeoLee
* @Date: 2019/12/30 17:36
* @Description:
* @version: 1.0
*/
public class Tests {
static class Person {
private String name;
private Byte[] bytes = new Byte[1024 * 1024];
public Person(String name) {
this.name = name;
}
}
public static void main(String[] args) throws InterruptedException {
Person person = new Person("张三");
SoftReference<Person> softReference = new SoftReference<>(person);
person = null; //去掉强引用,new Person("张三")的这个对象就只有软引用了
System.gc();
Thread.sleep(1000);
System.err.println("软引用的对象 ------->" + softReference.get());
}
}
结果如下:
由结果可以看出该软引用对象没有被gc()回收,表明,内存是够用的。
修改代码如下:
import java.lang.ref.SoftReference;
/**
* @Auther: LeoLee
* @Date: 2019/12/30 17:36
* @Description:
* @version: 1.0
*/
public class Tests {
static class Person {
private String name;
private Byte[] bytes = new Byte[1024 * 1024];
public Person(String name) {
this.name = name;
}
}
public static void main(String[] args) throws InterruptedException {
Person person = new Person("张三");
SoftReference<Person> softReference = new SoftReference<>(person);
person = null; //去掉强引用,new Person("张三")的这个对象就只有软引用了
// System.gc();
Person anotherPerson = new Person("李四");
Thread.sleep(1000);
System.err.println("软引用的对象 ------->" + softReference.get());
}
结果如下:
表明由于创建了第二个Person对象导致内存不足,软引用已经被回收了
弱引用
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期,它只能生存到下一次垃圾收集发生之前。当垃圾回收器扫描到只具有弱引用的对象时,无论当前内存空间是否足够,都会回收它。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
public static void main(String[] args) throws InterruptedException {
Person person = new Person("张三");
ReferenceQueue<Person> queue = new ReferenceQueue<>();
WeakReference<Person> weakReference = new WeakReference<Person>(person, queue);
person = null;//去掉强引用,new Person("张三")的这个对象就只有软引用了
System.gc();
Thread.sleep(1000);
System.err.println("弱引用的对象 ------->" + weakReference.get());
Reference weakPollRef = queue.poll(); //poll()方法是有延迟的
if (weakPollRef != null) {
System.err.println("WeakReference对象中保存的弱引用对象已经被GC,下一步需要清理该Reference对象");
//清理softReference
} else {
System.err.println("WeakReference对象中保存的软引用对象还没有被GC,或者被GC了但是获得对列中的引用对象出现延迟");
}
}
虚引用
作为最弱的引用类型,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。
public class PhantomReference<T> extends Reference<T> {
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
Object object = new Object();
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
引用队列(ReferenceQueue)
引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。
与软引用、弱引用不同,虚引用必须和引用队列一起使用。
参考资料: