Java Reference 详解 (强引用、软引用、弱引用、虚引用)

Java Reference 详解 (强引用、软引用、弱引用、虚引用)

 

 

 

1、简介

      像数据字典、用户信息等一些常用的数据,我们经常会放在应用缓存中。但假若放到缓存里的数据量非常大,那就会出现一个问题:由于数据量过大可能会导致内存不足,而不单单是提升性能了。假如说把表中所有数据都放入缓存,那么缓存的可能会占据大部分jvm的内存或者索性直接产生一个OOM错误。最佳的方案是如果我们可以创造一种可以按需扩展和收缩的动态缓存,当我们的数据量需要而内存充裕的时候可以适当增加,但内存不足是可以按不同方案对其进行回收。我们的应用在运行过程中会产生很多对象,这些对象驻留在内存中,它们大小不同,重要性不同,使用频率不同,生命周期不同,比如有些对象只要应用启动就一直存活直到应用停止,而有些对象生命周期与创建它的线程相同,还有些对象只作临时变量短时间就消亡。所以很容易想象对于不同的对象,我们希望对他们的创建销毁采取不同的策略。于是设计者们在java 1.2加入了reference,引入了强引用、软引用、弱引用、虚引用这四个概念。 使jvm可以对不同的reference对象采取不同的回收策略以达到提高应用性能的目的。

java.lang.ref 包中就有以下几种不同的reference类型。继承关系如下:

      

 

2、Reference 介绍 

reference构造函数

Reference(T referent) {
 this(referent, null);
}
  
Reference(T referent, ReferenceQueue<? super T> queue) {
 this.referent = referent;
 this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

       其内部提供2个构造函数,一个带queue,一个不带queue。其中queue的意义在于,我们可以在外部对这个queue进行监控.即如果有对象即将被回收,那么相应的reference对象就会被放到这个queue里。我们拿到reference,就可以再作一些事务。而如果不带的话,就只有不断地轮训reference对象,通过判断里面的get是否返回null(phantomReference对象不能这样作,其get始终返回null,因此它只有带queue的构造函数).这两种方法均有相应的使用场景,取决于实际的应用。如weakHashMap中就选择去查询queue的数据,来判定是否有对象将被回收。而ThreadLocalMap,则采用判断get()是否为null来作处理。Reference的直接子类都是由jvm定制化处理的, 因此在代码中直接继承于Reference类型没有任何作用,只能继承于它的子类, 相应的子类类型包括以下几种:

 

3、强引用 (StrongReference

     我们发现在类图中我们并没有发现 StrongReference 类型,原因是我们平时写的代码基本上都是 StrongReference 。我们最常的创建对象方式就是 new 一个对象,然后将其赋值给一个声明为这个对象的类型及其父类的引用。如果对象有一个 StrongReference ,那么这个对象将不会被gc回收。

举例 HelloWorld hello = new HelloWorld();  这里 hello 就是一个 HelloWorld 对象的 StrongReference。

 

4、软引用(SoftReference

       如果一个对象存在一个 SoftReference ,软引用在JVM报告内存不足的时候才会被GC回收,否则不会回收。正是由于这种特性,软引用在caching和pooling中用处广泛。软引用的用法:可以通过对对象的 SoftReference 调用 get() 方法获取该对象。如果这个对象没有被 gc 回收,则返回此对象,否则返回 null 。

Object obj = new Object();
SoftReference<Object> softRef = new SoftReference(obj);
// 使用 softRef.get() 获取软引用所引用的对象
Object objg = softRef.get();

 

5、弱引用(WeakReference

当GC时一但发现了弱引用对象,将会释放WeakReference所引用的对象,哪怕虚拟机的内存还足够多。弱引用使用方法与软引用类似,但回收策略不同。

 

6、虚引用 (PhantomReference 

     当GC一但发现了虚引用对象,将会将PhantomReference对象插入ReferenceQueue队列,而此时PhantomReference所指向的对象并没有被GC回收,而是要等到ReferenceQueue被你真正的处理后才会被回收。PhantomReference 不能用于直接访问对象。调用 get() 方法都会返回 null 。虚引用的用法:

Object obj = new Object();
ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
PhantomReference<Object> phanRef = new PhantomReference<Object>(obj, refQueue);
// 调用phanRef.get()不管在什么情况下会一直返回null
Object objg = phanRef.get();
// 如果obj被置为null,当GC发现了虚引用,GC会将phanRef插入进我们之前创建时传入的refQueue队列
// 注意,此时phanRef所引用的obj对象,并没有被GC回收,在我们显式地调用refQueue.poll返回phanRef之后
// 当GC第二次发现虚引用,而此时JVM将phanRef插入到refQueue会插入失败,此时GC才会对obj进行回收
Reference<? extends Object> phanRefP = refQueue.poll();

 

总结

      通过对以上各类型的 reference 介绍可以发现其实 reference 主要是用来与虚拟机 gc 进行交互,使得虚拟机根据对象的不同引用类型,对其采用不同的内存回收策略。strong 引用的对象正常情况下不会被回收,soft 引用的对象会在出现 OOM 错误之前被回收,而 weak 引用的对象在下一次 gc 的时候就会被回收。

 

posted @ 2023-06-17 12:30  邓维-java  阅读(734)  评论(0)    收藏  举报