可达性分析算法中描述的对象引用,一般指的是强引用,即是 GCRoot 对象对普通对象有引用关系,只要这层关系存在,普通对象就不会被回收。除了强引用就不会回收。除了强引用之外,Java中还设计了几种引用方式:
-
软引用
-
弱引用
-
虚引用
-
终结器引用
软引用
软引用相对于强引用是一种比较弱的引用关系,如果一个对象只有软引用关联到它,当程序内存不足时,就会将软引用中的数据进行回收。在JDK 1.2版提供了 SoftReference 类来实现软引用,软引用常用于缓存中。
如下图,对象A 被GC Root对象强引用了,同时我们创建了一个软引用 SoftReference 对象(它本身也是一个对象),软引用对象中引用了对象A.

接下来强引用被去掉之后,对象 A 暂时还是处于不可回收状态,因为有软引用存在并且内存还够用。

如果内存出现不够用的情况,对象A 就处于可回收状态,可以被垃圾回收器回收。

这样做有什么好处?如果对象A 是一个缓存,平时会保存在内存中,如果想访问数据可以快速访问。但是如果内存不够用了,我们就可以将这部分缓存清理掉释放内存。即便缓存没了,也可以从数据库等地方获取数据,不会影响到业务正常运行,这样可以减少内存溢出产生的可能性。
特别注意:
软引用对象本身,也需要被强引用,否则软引用对象也会被回收掉。

软引用的使用方法
软引用的执行过程如下:
1、将对象使用软引用包装起来,new SoftReference<对象类型>(对象)。
2、内存不足时,虚拟机尝试进行垃圾回收。
3、如果垃圾回收仍不能解决内存不足的问题,回收软引用的对象。
4、如果依然内存不足,抛出OutOfMemory 异常。
软引用对象本身怎么回收呢?
如果软引用对象里边包含的数据已经被回收了,那么软引用对象本身其实也可以被回收了。
SoftRefence提供了一套队列机制:
1、软引用创建时,通过构造器传入引用队列

2、在软引用中包含的对象被回收时,该软引用对象会被放入引用队列

3、通过代码遍历引用队列,将SoftReference的强引用删除
java
/**
* 软引用案例3 - 引用队列使用
*/
public class SoftReferenceDemo3 {
public static void main(String[] args) throws IOException {
ArrayList<SoftReference> softReferences = new ArrayList<>();
ReferenceQueue<byte[]> queues = new ReferenceQueue<byte[]>();
for (int i = 0; i < 10; i++) {
byte[] bytes = new byte[1024 * 1024 * 100];
SoftReference studentRef = new SoftReference<byte[]>(bytes,queues);
softReferences.add(studentRef);
}
SoftReference<byte[]> ref = null;
int count = 0;
while ((ref = (SoftReference<byte[]>) queues.poll()) != null) {
count++;
}
System.out.println(count);
}
}
//输出9
软引用的缓存案例
使用软引用实现学生信息的缓存,能支持内存不足时清理缓存。

java
package chapter04.soft;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
/**
* 软引用案例4 - 学生信息的缓存
*/
public class StudentCache {
private static StudentCache cache = new StudentCache();
public static void main(String[] args) {
for (int i = 0; ; i++) {
StudentCache.getInstance().cacheStudent(new Student(i, String.valueOf(i)));
}
}
private Map<Integer, StudentRef> StudentRefs;// 用于Cache内容的存储
private ReferenceQueue<Student> q;// 垃圾Reference的队列
// 继承SoftReference,使得每一个实例都具有可识别的标识。
// 并且该标识与其在HashMap内的key相同。
private class StudentRef extends SoftReference<Student> {
private Integer _key = null;
public StudentRef(Student em, ReferenceQueue<Student> q) {
super(em, q);
_key = em.getId();
}
}
// 构建一个缓存器实例
private StudentCache() {
StudentRefs = new HashMap<Integer, StudentRef>();
q = new ReferenceQueue<Student>();
}
// 取得缓存器实例
public static StudentCache getInstance() {
return cache;
}
// 以软引用的方式对一个Student对象的实例进行引用并保存该引用
private void cacheStudent(Student em) {
cleanCache();// 清除垃圾引用
StudentRef ref = new StudentRef(em, q);
StudentRefs.put(em.getId(), ref);
System.out.println(StudentRefs.size());
}
// 依据所指定的ID号,重新获取相应Student对象的实例
public Student getStudent(Integer id) {
Student em = null;
// 缓存中是否有该Student实例的软引用,如果有,从软引用中取得。
if (StudentRefs.containsKey(id)) {
StudentRef ref = StudentRefs.get(id);
em = ref.get();
}
// 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,
// 并保存对这个新建实例的软引用
if (em == null) {
em = new Student(id, String.valueOf(id));
System.out.println("Retrieve From StudentInfoCenter. ID=" + id);
this.cacheStudent(em);
}
return em;
}
// 清除那些所软引用的Student对象已经被回收的StudentRef对象
private void cleanCache() {
StudentRef ref = null;
while ((ref = (StudentRef) q.poll()) != null) {
StudentRefs.remove(ref._key);
}
}
// // 清除Cache内的全部内容
// public void clearCache() {
// cleanCache();
// StudentRefs.clear();
// //System.gc();
// //System.runFinalization();
// }
}
class Student {
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
弱引用
弱引用的整体机制和软引用基本一致,区别在于弱引用包含的对象在垃圾回收时,不管内存够不够都会直接被回收。在JDK 1.2版之后提供了 WeakReference类来实现弱引用,弱引用主要在ThreadLocal 中使用。
弱引用对象本身也可以使用引用队列进行回收。
虚引用和终结器引用哦
这两种引用在常规开发中是不会使用的。
虚引用也叫幽灵引用/幻影引用,不能通过虚引用对象获取到包含的对象,虚引用唯一的用途是当对象被垃圾回收器回收可以接收到对应的通知。Java中使用 PhantomReference实现了虚引用,直接内存中为了及时知道直接内存对象不再使用,从而回收内存,使用了虚引用来实现。
终结器引用指的是在对象需要被回收时,终结器引用会关联对象并放置在 Finalizer类中的引用队列中,在稍后由一条由FinalizerThread 线程从队列中获取对象,然后执行对象的finalize 方法,在对象第二次回收时,该对象才真正的被回收。在这个过程中可以在 finalize 方法中再将自身对象使用强引用关联上,但是不建议这样做。