在java中提供了四个级别的引用:强引用,软引用,弱引用和虚引用。
强引用:只要引用存在,垃圾回收器永远不会回收Object obj = new Object();
//可直接通过obj取得对应的对象 如obj.equels(new Object());
而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。这里不再解释。
软引用:软引用是比强引用弱一点的引用。一个对象只持有软引用,那么当堆空间不足时,就会被回收。下面展示软引用在内存不足时被回收的例子
package gcExample;
import java.lang.ref.SoftReference;
public class SoftRef {
public static class User {
public int id;
public String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "[id=" + String.valueOf(id) + ",name=" + name + "]";
}
}
public static void main(String[] args) {
User u = new User(1, "geym");
SoftReference<User> userSoftRef = new SoftReference<SoftRef.User>(u);
u = null;
System.out.println(userSoftRef.get());
System.gc();
System.out.println("After GC");
System.out.println(userSoftRef.get());
byte[] b = new byte[1024 *925 * 7];
System.gc();
System.out.println(userSoftRef.get());
}
}
运行上述代码得到:
[id=1,name=geym]
After GC
[id=1,name=geym]
null
因此得到结论:gc未必会回收软引用的对象,但是,当内存资源紧张时,软引用对象会被回收,所以软引用对象不会引起内存溢出。
每一个软引用都可以附带一个引用队列,当对象的可达性状态发生改变时(由可达变为不可达),软引用对象就会进入引用队列。通过这个队列,可以跟踪对象的回收情况,请看下面代码
package gcExample;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
public class SoftRefQ {
public static class User {
public int id;
public String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "[id=" + String.valueOf(id) + ",name=" + name + "]";
}
}
static ReferenceQueue<User> softQueue=null;
public static class CheckRefQueue extends Thread{
@Override
public void run(){
while(true){
if(softQueue!=null){
UserSoftReference obj=null;
try{
obj =(UserSoftReference) softQueue.remove();
}catch(InterruptedException e){
e.printStackTrace();
}
if(obj!=null){
System.out.println("user id "+ obj.uid +"is delete");
}
}
}
}
}
public static class UserSoftReference extends SoftReference<User>{
int uid;
public UserSoftReference(User referent,ReferenceQueue<? super User> q){
super (referent,q);
uid=referent.id;
}
}
public static void main(String[] args) throws InterruptedException{
Thread t= new CheckRefQueue();
t.setDaemon(true);
User u=new User(1,"geym");
softQueue= new ReferenceQueue<SoftRefQ.User>();
UserSoftReference userSoftRef = new UserSoftReference(u,softQueue);
u=null;
System.out.println(userSoftRef.get());
System.gc();
//內存足夠
System.out.println("After gc");
System.out.println(userSoftRef.get());
System.out.println(" try to create byte array and gc ");
byte [] b= new byte [1024* 925 *7];
System.gc();
System.out.println(userSoftRef.get());
Thread.sleep(1000);
}
}
上述代码extends SoftReference 实现了一个自定义的软引用类,扩展软引用的目的是记录User.uid,后续再引用队列中,就可以通过这个uid字段知道哪个User实例被回收了。
值得注意的是,在创建软引用时(UserSoftReference userSoftRef = new UserSoftReference(u,softQueue),制定了一个软引用队列,当给定的对象实例被回收时,就会被加入这个引用队列,通过访问该队列可以跟踪对象的回收情况。
上述代码执行结果:
[id=1,name=geym]
After gc
[id=1,name=geym]
try to create byte array and gc
user id 1 is delete
null
弱引用:
弱引用是一种比软引用较弱的引用类型,在系统gc时,只要发现弱引用,不管系统堆空间使用情况怎么样,都会将对象进行回收。但是由于垃圾回收器的线程通常优先级很低,因此,斌那个不一定能很快的发现持有弱引用的对象。在这种情况下,弱引用对象可以存较长的时间。一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册的引用队列中(和软引用很像)下面给出例子
package gcExample;
import java.lang.ref.WeakReference;
public class WeakRef {
public static class User {
public int id;
public String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "[id=" + String.valueOf(id) + ",name=" + name + "]";
}
}
public static void main(String[] args) {
User u = new User(1, "geym");
WeakReference<User> userWeakRef = new WeakReference<User>(u);
u = null;
System.out.println(userWeakRef);
System.gc();
// 不管当前内存空间是否够用 ,都会回收它的内存
System.out.println("after gc");
System.out.println(userWeakRef.get());
}
}
执行结果:
java.lang.ref.WeakReference@2a139a55
after gc
null
弱引用和软引用一样,在构造弱引用时,也可以指定一个引用队列,当弱引用对象被回收时,就会加入指定的引用队列,通过这个队列可以跟踪对象的回收情况
注意:软引用、弱引用都非常适合来保存那些可有可无的缓存数据。如果这么做,当系统内存不足时,这些缓存数据会被回收。不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。
虚引用:
虚引用是所有引用类型中最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。当试图通过虚引用get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,作用在于跟踪垃圾回收过程。
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序对象的回收情况。
下面给出实例,使用虚引用跟踪一个可复活对象的回收
package gcExample;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class TraceCanReliveObj {
public static TraceCanReliveObj obj;
static ReferenceQueue<TraceCanReliveObj> phantomQueue = null;
public static class CheckRefQueue extends Thread {
@Override
public void run() {
while (true) {
if (phantomQueue != null) {
PhantomReference<TraceCanReliveObj> objt = null;
try {
objt = (PhantomReference<TraceCanReliveObj>) phantomQueue
.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (objt != null) {
System.out.println("TraceCanReliveObj is delete by gc");
}
}
}
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("CanReliveObj finalize called ");
obj = this;
}
@Override
public String toString() {
return " i am CanReliveObj";
}
public static void main(String[] args) throws InterruptedException {
Thread t = new CheckRefQueue();
t.setDaemon(true);
t.start();
phantomQueue = new ReferenceQueue<TraceCanReliveObj>();
obj = new TraceCanReliveObj();
PhantomReference<TraceCanReliveObj> phantomRef = new PhantomReference<TraceCanReliveObj>(
obj, phantomQueue);
obj = null;
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println(" obj is null");
} else {
System.out.println(" obj 可用");
}
System.out.println("第二次gc");
obj = null;
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println(" obj is null");
} else {
System.out.println(" obj 可用");
}
}
}
执行上述代码得到如下输出:
CanReliveObj finalize called
obj 可用
第二次gc
TraceCanReliveObj is delete by gc
obj is null
由于虚引用可以跟踪对象的回收时间,因此,也可以将一些资源释放操作放置在虚引用中执行和记录。