一、概述
Java的垃圾回收(GC)
是虚拟机自动管理的,
Java内存管理分为内存分配和内存回收,都不需要程序员负责,垃圾回收的机制主要是看对象是否有引用指向它。
Java对象的引用类型包括:强引用,软引用,弱引用,虚引用,Java提供这四种引用类型主要有两个目的:
- 可以让程序员通过代码的方式决定某些对象的生命周期。
- 更有利于JVM进行垃圾回收。
二、四种引用类型
1. 强引用(StrongReference)
强引用是指创建一个对象把它赋值给一个引用,引用是存在于JVM
的栈中的。
强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。
例如:
@Test
fun testStrongReferences(){
var obj: Any? = Any()
}
2. 软引用(SoftReference)
软引用的对象只有在内存不足的情况下,被引用的对象才会被回收。
SoftReference的定义:
public class SoftReference<T> extends Reference<T>
SoftReference继承自Reference。有两种构造方法:
public SoftReference(T referent)
public SoftReference(T referent, ReferenceQueue<? super T> q)
- 第一个参数就是软引用的对象。
- 第二个参数叫做ReferenceQueue,是用来存储封装的待回收Reference对象的,ReferenceQueue中的对象是由Reference类中的ReferenceHandler内部类进行处理的。 例如:
@Test
fun testSoftReference() {
var obj: Any? = Any()
val soft = SoftReference(obj)
obj = null
println("${soft.get()}")
System.gc()
println("${soft.get()}")
}
输出结果:
java.lang.Object@3b764bce
java.lang.Object@3b764bce
可以发现在内存充足的情况下,SoftReference引用的对象是不会被回收的。
3. 弱引用(WeakReference) WeakReferencee引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。
WeakReference继承自Reference。有两种构造方法:
public WeakReference(T referent);
public WeakReference(T referent, ReferenceQueue<? super T> q);
含义和SoftReference一致。
例如:
@Test
fun testWeakReference() {
var obj: Any? = Any()
val weak = WeakReference(obj)
obj = null
println(" ${weak.get()}")
System.gc()
println(" ${weak.get()}")
}
输出结果:
java.lang.Object@3b764bce
null
我们看到gc过后,弱引用的对象就会被回收掉了。
4. 虚引用(PhantomReference)
作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用ReferenceQueue.poll()方法,将引用ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。
和SoftReference,WeakReference不同,PhantomReference只有一个构造函数,必须传入ReferenceQueue:
public PhantomReference(T referent, ReferenceQueue<? super T> q)
例如:
@Test
fun testPhantomReference(){
var obj: Any? = Any()
val queue = ReferenceQueue<Any>()
val phantom = PhantomReference<Any>(obj,queue)
obj = null
println(" ${phantom.get()}")
System.gc()
println(" ${queue.poll()}")
}
输出结果:
null
java.lang.ref.PhantomReference@3b764bce
可以看到get()值是null,而GC过后,poll是有值的。
因为PhantomReference引用的是需要被垃圾回收的对象,所以在类的定义中,get一直返回null:
public T get() { return null; }
三、Reference和ReferenceQueue
讲完上面的四种引用,接下来我们谈一下他们的父类Reference和ReferenceQueue的作用。
Reference是一个抽象类,每个Reference都有一个指向的对象,在Reference中有5个非常重要的属性:referent,next,discovered,pending,queue。
每个Reference都可以看成是一个节点,多个Reference通过next,discovered和pending这三个属性进行关联。
先用一张图来对Reference有个整体的概念:
referent就是Reference实际引用的对象。
通过next属性,可以构建ReferenceQueue。
通过discovered属性,可以构建Discovered List。
通过pending属性,可以构建Pending List。
1. 关于状态说明
因为Reference有两个构造函数,一个带ReferenceQueue,一个不带。
对于带ReferenceQueue的Reference,GC会把要回收对象的Reference放到ReferenceQueue中,后续该Reference需要程序员自己处理(调用poll方法)。
不带ReferenceQueue的Reference,GC自己处理,待回收的对象Reference状态会变成Inactive。
-
创建好的Reference,就进入active状态。
-
active状态下,如果引用对象的可到达状态发送变化就会转变成Inactive或Pending状态。
-
在到达Inactive状态的Reference状态不能被改变,会等待GC回收。
-
在Pending状态的等待进入Queue,Reference内部有个ReferenceHandler,会调用enqueue方法,将Pending状态的对象放入到Queue中。
-
进入Queue的对象,其状态就变成了Enqueued。
-
Enqueued状态的对象,如果调用poll方法从ReferenceQueue拿出,则该Reference的状态就变成了Inactive,等待GC的回收。
这就是Reference的一个完整的生命周期。
2. 一个Queue,三个List
有了上面四个状态的概念,我们接下来讲一个Queue,三个List:ReferenceQueue,discovered List和pending List。
-
ReferenceQueue在讲状态的时候已经讲过了,它本质是由Reference中的next连接而成的。用来存储GC待回收的对象。
-
pending List就是待入ReferenceQueue的list。
-
discovered List有点特别,在Pending状态时候,discovered List就等于pending List。
-
在Active状态的时候,discovered List实际上维持的是一个引用链。通过这个引用链,我们可以获得引用的链式结构,当某个Reference状态不再是Active状态时,需要将这个Reference从discovered List中删除。