知识点的梳理:
-
可触及性包含3种状态:对象只有在不可触及状态时才会被回收!;
- 可触及的:从根节点开始,可以到达这个对象;
- 可复活的:对象的所有引用都被释放,但是对象有可能在finalize()函数中复活;
- 不可触及的:对象的finalize()函数被调用,并且没有复活,就会进入不可触及状态。该状态的对象不可能被复活,因为finalize()函数只会被调用一次;
-
Java 提供4个级别的引用:强引用,软引用,弱引用,虚引用;
-
说明
- 可触及性的必要性:不被使用的对象,很有可能借助finalize()函数复活自己,所以我们需要一个状态来判定一个对象到底是否可回收!
- 示例1:对象的复活
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 CanReliveObj"; } public static void main(String[] args) throws InterruptedException { obj = new CanReliveObj(); //第一次清除对象,对象通过finalize()复活 obj = null; System.gc(); Thread.sleep(1000); if(obj == null){ System.out.println("obj是null"); }else{ System.out.println("obj 可用"); } System.out.println("第2次GC"); //第二次清除对象,无法再复活 obj = null; System.gc(); Thread.sleep(1000); if(obj == null){ System.out.println("obj是null"); }else{ System.out.println("obj 可用"); } } } |
-
引用和可触及性的强度
-
软引用,弱引用,虚引用可在java.lang.ref包中找到它们。FinalReference表示"最终"引用,它用于实现对象的finalize()方法
-
强引用
-
能力:程序中一般的引用类型,此级别的对象是可触及的,不会被回收;
-
能力特点:
- 可直接访问目标对象;
- 强引用指向的对象在任何时候都不会被回收,虚拟机宁愿抛出OOM异常,也不会回收强引用所指向的对象;
- 强引用可能导致内存泄漏;
-
- 示例:
-
-
步骤1 : StringBuffer str = new StringBuffer("Hello World"); |
说明:假设此段代码在函数体内运行,那么局部变量str将被分配在栈上,而对象StringBuffer实例被分配在堆上。局部变量str指向StringBuffer实例所在堆空间,通过str可以操作该实例,那么str就是StringBuffer实例的强引用 |
步骤2:StringBuilder str1 = str; |
此时str1指向str指向的对象,同时在局部变量表上会分配空间存放str1变量。此时,该StringBuffer示例就有两个引用。对引用的"=="操作用于表示两操作数所指向的堆空间地址是否相同,不表示两操作数所指向的对象是否相等 |
这两个引用都是强引用 |
-
软引用--- 可被回收的引用
-
能力:比强引用弱一点的,软可触及的引用类型;
-
能力特点:
- 堆空间不足时,会被回收;
- 使用java.lang.SoftReference类实现;
-
- 示例1:软引用在系统堆内存不足时被回收
-
public class SoftRef { //声明User类 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 "User [id=" + id + ", name=" + name + "]"; } } public static void main(String[] args) { User u = new User(1,"geym");//建立user实例,强引用 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());//从软引用获取数据 } } | 运行参数:-Xmx10m User [id=1, name=geym](第一次从软引用中获取数据) After GC: User [id=1, name=geym](GC没有清除软引用) |
- 示例2:每个软引用都可以附带一个引用队列,当对象的可达性状态发生改变时(由可达变为不可达),软引用对象就会进入引用队列。通过该队列,可跟踪对象的回收情况
public class SoftRefQ { 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 "User [id=" + 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(Exception e){ e.printStackTrace(); } if(obj != null){ System.out.println("user id" + obj.uid+" is delete"); } } } } } //实现自定义软引用类,扩展软引用的目的是记录User.uid,在后续引用队列中,可以通过这个uid知道哪个User实例被回收了 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");//建立user实例,强引用 softQueue = new ReferenceQueue<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); } } | 使用如下参数执行此段代码:-Xmx10m User [id=1, name=geym](第一次从软引用获得对象) After GC: User [id=1, name=geym](GC后,软引用对象没有回收) try to create byte array and GC(创建大数组,耗尽内存) user id1 is delete(引用队列探测到对象被删除) null(对象已被回收,无法再通过软引用获取对象) |
-
弱引用---发现即回收
-
能力:弱可触及的引用类型。比软引用稍弱。只要发现弱引用,不管系统堆空间使用情况如何,都会将对象进行回收。
-
能力特点:
- 垃圾回收器线程优先级很低,因此,并不一定能很快发现持有弱引用的对象。这种情况下,弱引用对象可以存在较长的时间;
- 一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册的引用队列中;(这一点和软引用一样,可查看软引用的代码示例)
- 使用java.lang.ref.WeakReference类实现
-
能力分析:
- 软引用,弱引用都适合来保存那些可有可无的缓存数据。这样做,可以让系统内存不足时,让这些缓存数据被回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用;
-
- 示例1:
-
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 "User [id=" + id + ", name=" + name + "]"; } } public static void main(String[] args) { User u = new User(1,"hey"); //构造弱引用 WeakReference<User> userWeakRef = new WeakReference<User>(u); u = null;//去除强引用 System.out.println(userWeakRef.get());//从弱引用中重新获取对象 System.gc();//GC //不管当前内存空间足够与否,都会回收它的内存 System.out.println("After GC:"); System.out.println(userWeakRef.get());//重新尝试从弱引用中获取对象 } } | 运行结果: User [id=1, name=hey] After GC: null |
-
虚引用--- 对象回收跟踪
-
能力:虚可触及的引用了类型。所有应用类型中最弱的一个。持有虚引用的对象,和没有引用几乎是一样的。随时都可能被垃圾回收器回收。
-
能力特点:
- 试图通过虚引用的get()方法取得强引用时,总是会失败;
- 虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程;
- 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,已通知应用程序对象的回收情况;
-
- 示例1:
-
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(Exception 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是null"); }else{ System.out.println("obj可用"); } System.out.println("第2次GC"); obj = null; System.gc(); Thread.sleep(1000); if(obj == null){ System.out.println("obj是null"); }else{ System.out.println("obj可用"); } } } | 执行效果: CanReliveObj finalize called(对象复活) obj可用 第2次GC(第2次,对象无法复活) TraceCanReliveObj is delete by GC(引用队列捕获到对象被回收) obj是null |