4.3 谁才是真正的垃圾:判断可触及性
可触及性可以包含以下3种状态。
- 可触及的:从根节点开始,可以达到这个对象
- 可复活的:对象的所有引用都被释放,但是对象有可能在finalize函数中复活
- 不可触及的:对象的finalize函数被调用,并且没有复活,那么救火进出不可触及状态,不可触及的对象不可能被复活,因为finalize函数只会被调用一次。
以上3种状态中,只有在对象不可触及时才可被回收。
4.3.1 对象的复活
public class CanReliveObj {public static CanReliveObj obj;@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("CanReliveObj.finalize called");obj = this;}@Overridepublic String toString() {return "I am 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 可用");}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 可用");}}}
4.3.2 引用和可触及性的强度
Java提供了4个级别的引用:强引用、软引用、弱引用和虚引用。除了强引用外,其他3种引用均可以在java.lang.ref包中找到他们的身影。
强引用就是程序一般使用的引用类型,强引用的对象是可触及的,不会被回收。相对的,软引用、弱引用和虚引用的对象是软可触及和虚可触及的,在一定条件下,都是可用被回收的。
通过引用,可以对堆中的对象进行操作。在某函数中,当创建了一个对象,该对象被分配在堆中,通过这个对象的引用才能对这个对象进行操作。如
StringBuffer str= new StringBuffer("hello java");
假设以上代码是在函数体内运行的,那么局部变量str将被分配在栈上,而对象StringBuffer实例,被分配在堆上。局部变量str指向StringBuffer实例所在堆空间,通过str可以操作该实例,那么str就是StringBuffer的引用
此时,如果运行一个赋值语句
StringBuffer str1=str;
str所指向的对象也将被str1所指向。同时在局部空间上会分配空间存放str1变量。如下图。
上例中的两个引用,都是强引用,强引用具备以下特点。
- 强引用可以直接访问目标对象
- 强引用锁指向的对象在任何时候都不会被系统回收,JVM宁愿抛出OOM异常,也不回收强引用所指向的对象。
- 强引用可能导致内存泄漏
4.3.3 软引用——可被回收的引用
一个对象只持有软引用,那么当堆空间不足时,就会被回收。
下面代码演示软引用在系统堆内存不足时被回收
*** @author MI* @version 1.0* @date 2021/6/2 22:19* -Xmx10m*/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;@Overridepublic String toString() {return "[id=" + id + ",name=" + name + "]";}}public static void main(String[] args) throws InterruptedException {User u = new User(1, "geym");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 * 500 * 2];System.gc();Thread.sleep(1000);System.out.println(userSoftRef.get());}}
使用参数-Xmx10m 运行。我使用的是JDK9
[id=1,name=geym] (第一次从软引用中获取数据)After GC:[id=1,name=geym] (GC没有清除软引用)null (由于内存紧张,软引用被清除)
GC未必会回收软引用的对象,但是,当内存资源紧张时,软引用对象会被回收,所以软引用对象不会引起内存溢出。
4.3.4 弱引用——发现即回收
**
弱引用是一种以软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间使用情况如何,都会将对象进行回收。但是由于垃圾回收器的线程通常优先级很低,因此,并不一定能很快地发现持有弱引用的对象。在这种情况下,弱引用对象可以存在较长的时间。一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册的引用队列中。
/*** 不管当前内存空间足够与否,都会回收它的内存* @author geym**/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;@Overridepublic 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());}}
