随笔分类
再谈引用
我们希望能够描述这样一类对象:当内存足够时,则能保留在内存中;当内存空间进行垃圾回收后还是很紧张,则可以对某些对象进行回收
JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)
、软引用(Soft Reference)
、弱引用(Weak Reference)
、和虚引用(Phantom Refernece)
,这四种引用强度依次减弱
java.lang.ref
强引用
StrongReference
最传统的"引用定义",是指在程序代码之中普遍存在的引用赋值,即类似:Obejct o = new Obejct() 这种引用关系.
无论什么情况下,只要强引用关系存在,垃圾回收器永远不会回收掉被引用的对象.
在Java程序中,最常见的引用类型是强引用(普通程序99%以上都是强引用),也就是我们最常见的普通对象引用,也是默认的引用类型
.
当在Java语言中使用new操作符创建一个新的对象,并将其赋值给一个变量的时候,这个变量就成为指向该对象的一个强引用.
强引用的对象是可触及的,垃圾收集器就永远不会回收掉被引用的对象。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,就是可以当做垃圾被收集了,当然具体回收时机还是要看垃圾收集策略。
相对的, 软引用、弱引用和虚引用的对象是软可触及、弱可触及和虚可触及的,在一定条件下,都是可以被回收的。所以,强引用是造成Java内存泄漏的主要原因之一
特点
- 强引用可以直接来访问目标对象
- 强引用所指向的目标对象在任何时候都不会被系统回收,JVM宁愿抛出OOM,也不会去回收强引用指向的目标对象
- 强引用可能会导致内存泄漏
软引用
SoftReference
public class SoftReference<T> extends Reference<T> {
...
}
是用来描述一些还有用,但非必须的对象.
在系统将要发生内存溢出
之前,将会把这些对象列入回收范围中进行二次回收(第一次回回收的是垃圾)
. 如果这次回收后还没有足够的内存,才会抛出内存溢出异常.
软引用本身不会导致内存溢出
软引用通常用来实现内存敏感的缓存
,比如:告诉缓存就有用到软引用. 如果还有空余内存,就可以暂时保留缓存;当内存不足时,就会去清除缓存,这样就可以保证使用缓存的同时,不会耗尽内存.
垃圾回收器在某个时刻决定回收软可达的对象时,会清理软引用,并可选地把软引用存放到一个引用队列(Reference Queue)
类似弱引用,只不过Java虚拟机会尽量让软引用的存活时间长一些,迫不得已才去清理.
演示
/**
* -Xms10m -Xmx10m
*
* User{name='liangye', age=20}
* Try Second GC
* null
* Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
* at SoftRef.main(SoftRef.java:16)
*/
public class SoftRef {
public static void main(String[] args) {
User user = new User("liangye", 20);
SoftReference<Object> softReference = new SoftReference<>(user); //建立弱引用
user = null; //取消强引用.
System.out.println(softReference.get()); //从软引用中获取对象的数据
System.out.println("Try Second GC");
try { //让JVM以为内存不够
byte[] bytes = new byte[1024 * 1024 * 7];
} catch (Exception e) {
e.printStackTrace();
} finally {
//在报OOM之前,垃圾回收器会去回收软引用的对象.
System.out.println(softReference.get()); //null
}
}
}
class User {
public User(String name, int age) {
this.name = name;
this.age = age;
}
String name;
int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
弱引用
WeakReference
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
发现即回收
用来描述那些非必需对象
只被弱引用所关联着对象只能生存到下一次垃圾收集之前. 当垃圾收集器工作时,无论内存空间是否足够
,都会回收掉被弱引用关联的对象.
但是, 由于垃圾回收器的线程通常优先级都很低,因此,并不一定能很快地发现持有弱引用的对象. 在这种情况下,弱引用对象可以存在较长的时间.
软引用和弱引用一样,在构造弱引用时,也可以来指定一个引用队列,当弱引用对象被回收时,就会加入到指定的引用队列,通过这个队列可以跟踪对象的回收情况.
软引用、弱引用都非常适合用来保存那些可有可无的缓存数据(内存敏感)
,这样的话,当系统内存不足时,这些缓存数据就会被回收,不会导致内存溢出. 而当内存充足时,这些数据又可以存在相当长的时间,从而起到加速系统的作用.
/**
* User{name='liangye', age=18}
* null
*/
public class WeakRef {
public static void main(String[] args) {
User user = new User("liangye", 18);
WeakReference<User> weakReference = new WeakReference<>(user);
user = null;
System.out.println(weakReference.get());
System.gc(); //发现及回收
System.out.println(weakReference.get());
}
}
区别软引用
- 弱引用对象和软引用对象的最大不同在于,当GC在进行回收时,需要通过算法检查是否回收软引用对象,而对于弱引用对象,GC总是进行回收. 弱引用对象更容易、更快被GC回收.
虚引用
PhantomReference
public class PhantomReference<T> extends Reference<T> {
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
对象回收跟踪
幽灵引用,幻影引用
是所有引用类型中最弱的一个.
一个对象是否有虚引用的存在,完全不会对其生存时间构成影响
,也无法通过一个虚引用来获得一个对象的实例.
为一个对象设置虚引用关联的目的就是能在这个对象被收集器回收时收到一个系统通知
.
它不能单独使用,也无法通过虚引用来获取被引用的对象. 当试图通过虚引用的get()获取对象时,总是null.
- 虚引用必须和引用队列一起使用. 虚引用在创建时必须提供一个
引用队列
作为参数. 当垃圾收集器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象之后,将这个虚引用加入到引用队列,以来通知应用程序对象的回收情况. - 由于虚引用可以
跟踪对象的回收时间
,因此,也可以将一些资源释放操作放置到虚引用中执行和记录.
/**
* null
* 虚引用已经加入到该队列中
*/
public class PhantomRef {
static ReferenceQueue referenceQueue = new ReferenceQueue();
public static void main(String[] args) throws InterruptedException {
User user = new User("liangye", 18);
PhantomReference<User> phantomReference = new PhantomReference<>(user, referenceQueue);
user = null;
System.out.println(phantomReference.get()); //null,虚引用是拿不到对象的.
System.gc();
Thread.sleep(1000);
if (referenceQueue.remove() != null) {
System.out.println("虚引用已经加入到该队列中");
}
}
}
终结器引用
FinalReference
class FinalReference<T> extends Reference<T> {
public FinalReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
用来实现对象的finalize(),也可以称为终结器引用.
无需手动编码,其内部配合引用队列使用.
在GC时,终结器引用入队. 有Finalizer线程通过终结器引用找到被引用的对象并调用其finalize(),第二次GC时才能够回收被引用的对象.