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方法只会调用一次,第二次会被回收,同时,其引用队列应该也会捕获到对象的回收,输出结果如下: