java中垃圾回收的基本思想是考察每一个对象的可达性,即从根节点出发是否可以被访问到这个对象,如果这个对象被访问到说明这个对象正在被使用,如果所有的节点都无法获取到整个对象,那么说明这个对象不再被使用了,一般说来这种对象是需要被回收的,但是,一个这样的对象在某些条件下复活了自己,如果这样的话那么对他的回收是不合理的,那么我们可以定义如下的状态来确保这个对象可以被回收,而且不会引起其他的异常。
1.首先,这个对象是不可触及的,也就是说这个对象不能够通过根节点被访问到,这是一个首要的条件。
2.其次这个对象不能够复活自己,也就是说假如这个对象的所有的引用都被释放了,但是对象可能在finalize方法中复活,那么这个对象就不能够进行回收,也就是说,对象也同时应该满足不能够复活自己的条件。
能够达到这两个条件那么这个对象便进入了不可达到的状态,如果复活了自己,再次进行回收的时候也能够进行,这是因为finalize方法只调用一次。
体会一个对象复活自己的例子:
public class CanReliveObj { public static CanReliveObj obj; @Override protected void finalize() throws Throwable{ super.finalize(); System.out.println("CanReliveObj finalize called"); obj = this; } @Override public String toString(){ return "I am a CanReliveObj"; } public static void main(String[] args) throws InterruptedException{ obj = new CanReliveObj(); obj = null; System.gc(); Thread.sleep(1000); if(obj == null){ System.out.println("Obj 是 null"); }else{ System.out.println("Obj 可用"); } /** * finalize()方法每次只能在释放销毁对象的时候被调用一次, * 所以第一次会调用重写的方法让对象复活 obj=this. * 但是,当第二次调用的时候就是原来的finalize方法 * 也就将对象释放 */ System.out.println("第二次 gc"); obj = null; System.gc(); Thread.sleep(1000); if(obj == null){ System.out.println("Obj 是 null"); }else{ System.out.println("Obj 可用"); } } }运行结果如下:
可以看到在代码中将obj设置为null之后进行GC结果发现obj对象复活了,然而在下面的代码中再次释放GC,此时obj才真正的被回收。这是因为第一次GC时,在finalize方法调用之前虽然将obj设置为null,其在系统中已经被清除,但是finalize方法中对象的this引用依然会被传入到方法的内部,如果引用外泄那么对象就会复活,此时变为可触及的状态,但是finalize方法只会调用一次,因此在我们的第二次进行GC的时候,对象就不会再复活了,对象就被回收了,obj也变为了null。其实finalize方法是一个不太好的模式,因为它不是很适合释放资源,其方法内如果有复活对象自己的代码,那么就会在无意中复活自己,其次,这个方法是被系统调用的,调用的时间并不是明确的,所以不推荐使用这个方法进行资源回收,如果使用的话,尽量在try···catch···finally语句块中使用。
java中有四个级别的引用,其与对象的垃圾回收有着紧密的关系,认识一下这4中引用:
1.强引用
StringBuffer str = new StringBuffer("Hello world");假设我们写的代码实在函数体内运行的,那么局部变量str就会分布在栈上,而对象StringBuffer实例就会被分布在堆上,局部变量str指向StringBuffer实例所在的堆空间,通过str来操作这个实例,那么str就是StringBuffer实例的强引用。
StringBuffer str1 = str;那么str所指的对象也将会被str1所指向,同时在局部变量表上会分配空间存放str1变量,此时StringBuffer实例就有两个同时指向自己的引用,对引用使用“==”操作,可以看到结果相等,这说明两个指向了同一个内存地址,但并不表示两个操作数所指向的对象相等。
2.软引用
软引用是比强引用稍微弱一点的引用类型,一个对象 仅仅持有软引用,那么当内存空间不足的时候,就会释放这些内容,回收空间,软引用使用java.lang.ref.SoftReference来实现,下面的例子将会说明在空间不足的时候就会回收软引用。public class SoftRef { public static class User{ public User(int id,String name){ this.id = id; this.name = name; } public int id; public String name; @Override public String toString(){ return "id=" +String.valueOf(id) +", name=["+name+"]"; } } public static void main(String[] args){ User u = new User(1,"gym"); SoftReference<User> userSoftRef = new SoftReference<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()); } }代码中建立了User类的实例,同时将u变量为强引用,通过强引用建立了弱引用,userSoftRef;在代码中将u设置为null,取消所有的强引用,将其设置为一个仅有软引用的对象,在下面的代码中我们重新获得强引用对象,之后进行GC,在此时再次使用软引用获取到了强引用的对象,然而在我们再次开辟一个大的内存的时候,系统资源紧张,我们通过一次GC模拟系统进行GC,其实这个是多余的,因为在分配较大的数据时,系统会自动的进行GC,这里仅是为了说明问题,我们再次从软引用获取软引用时,已经失效,说明软引用被回收,所以也就是说软引用不会引起系统的内存溢出现象,每一个软引用都会附带一个引用队列,当对象的可达性状态发生改变时,软引用对象就会进入引用队列,通过这个引用队列可以跟踪对象的回收情况。代码如下:
public class SoftRefQue { public static class User{ public User(int id,String name){ this.id = id; this.name = name; } public int id; public String name; @Override public String toString(){ return "id=" +String.valueOf(id) +", name=["+name+"]"; } } static ReferenceQueue<User> softRefQue =null; public static class CheckRefQueue extends Thread{ @Override public void run(){ while(true){ if(softRefQue != null){ UserSoftReference obj = null; try{ obj = (UserSoftReference) softRefQue.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); t.start(); User u = new User(1,"geym"); softRefQue = new ReferenceQueue<User>(); UserSoftReference userSoftRef = new UserSoftReference(u, softRefQue); 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); } }值得注意的地方有两个:我们自己定义了一个自定义的软引用类,它继承了SoftReference类,为的是能够了解到使用记录User.uid知道哪个实例被回收了;下一个就是我们在构造这个软引用时使用了ReferenceQueue软引用队列实例化对象,这样,当软引用对象被回收时,这个对象就会加入到这个引用队列,也就是说我们追踪是要基于ReferenceQueue来实现的,我们指定堆的大小-Xmx10m
这说明再开辟一个较大的内存时,我们的user的软引用进行了回收,同时以由于开辟的内存较大,已经出超出了我们定义的堆的大小10m,所以随后跑出了一个OOM错误,但是,这个说明了当我们预留的内存不够为新的内容开辟时,就会回收软引用对象,不过,加入一个对象有很多的软引用,但是其有一个强引用,那么GC就不会回收这些软引用对象。
3.弱引用
public class WeakRef { public static class User{ public User(int id,String name){ this.id = id; this.name = name; } public int id; public String 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.get()); System.gc(); //不管当前的内存是否够用,都会回收 System.out.println("After GC"); System.out.println(userWeakRef.get()); } }代码中,我们将u的强引用去掉,并且在这个操作之前通过强引用构建了弱引用,之后开始进行GC,执行结果如下:
4.虚引用
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(); obj = this; } @Override public String toString(){ return "I am a 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 是 null"); }else{ System.out.println("obj 可用"); } System.out.println("第二次GC"); obj = null; System.gc(); Thread.sleep(1000); if(obj == null){ System.out.println("Obj 是 null"); }else{ System.out.println("obj 可用"); } } }代码中TraceCanReliveObj对象是一个在finalize方法中可以复活的对象,代码中构造了这个对象的虚引用,并且指定了对应的引用队列,然后将强引用去除,第一次GC 和之前看的例子一样,这个对象会复活,所以GC无法回收这个对象,接着,代码中再次进行GC,由于finalize方法只会调用一次,第二次会被回收,同时,其引用队列应该也会捕获到对象的回收,输出结果如下: