电子书下载地址

4.3 谁才是真正的垃圾:判断可触及性

可触及性可以包含以下3种状态。

  • 可触及的:从根节点开始,可以达到这个对象
  • 可复活的:对象的所有引用都被释放,但是对象有可能在finalize函数中复活
  • 不可触及的:对象的finalize函数被调用,并且没有复活,那么救火进出不可触及状态,不可触及的对象不可能被复活,因为finalize函数只会被调用一次。

以上3种状态中,只有在对象不可触及时才可被回收。

4.3.1 对象的复活

  1. public class CanReliveObj {
  2. public static CanReliveObj obj;
  3. @Override
  4. protected void finalize() throws Throwable {
  5. super.finalize();
  6. System.out.println("CanReliveObj.finalize called");
  7. obj = this;
  8. }
  9. @Override
  10. public String toString() {
  11. return "I am CanReliveObj";
  12. }
  13. public static void main(String[] args) throws InterruptedException {
  14. obj = new CanReliveObj();
  15. obj = null;
  16. System.gc();
  17. Thread.sleep(1000);
  18. if (obj == null) {
  19. System.out.println("obj 是 null");
  20. } else {
  21. System.out.println("obj 可用");
  22. }
  23. System.out.println("第2次gc");
  24. obj = null;
  25. System.gc();
  26. Thread.sleep(1000);
  27. if (obj == null) {
  28. System.out.println("obj 是 null");
  29. } else {
  30. System.out.println("obj 可用");
  31. }
  32. }
  33. }

4.3.2 引用和可触及性的强度

Java提供了4个级别的引用:强引用、软引用、弱引用和虚引用。除了强引用外,其他3种引用均可以在java.lang.ref包中找到他们的身影。

强引用就是程序一般使用的引用类型,强引用的对象是可触及的,不会被回收。相对的,软引用、弱引用和虚引用的对象是软可触及和虚可触及的,在一定条件下,都是可用被回收的。
通过引用,可以对堆中的对象进行操作。在某函数中,当创建了一个对象,该对象被分配在堆中,通过这个对象的引用才能对这个对象进行操作。如

  1. StringBuffer str= new StringBuffer("hello java");

假设以上代码是在函数体内运行的,那么局部变量str将被分配在栈上,而对象StringBuffer实例,被分配在堆上。局部变量str指向StringBuffer实例所在堆空间,通过str可以操作该实例,那么str就是StringBuffer的引用
image.png

此时,如果运行一个赋值语句

  1. StringBuffer str1=str;

str所指向的对象也将被str1所指向。同时在局部空间上会分配空间存放str1变量。如下图。
image.png

上例中的两个引用,都是强引用,强引用具备以下特点。

  • 强引用可以直接访问目标对象
  • 强引用锁指向的对象在任何时候都不会被系统回收,JVM宁愿抛出OOM异常,也不回收强引用所指向的对象。
  • 强引用可能导致内存泄漏

4.3.3 软引用——可被回收的引用

一个对象只持有软引用,那么当堆空间不足时,就会被回收。

下面代码演示软引用在系统堆内存不足时被回收

  1. **
  2. * @author MI
  3. * @version 1.0
  4. * @date 2021/6/2 22:19
  5. * -Xmx10m
  6. */
  7. public class SoftRef {
  8. public static class User {
  9. public User(int id, String name) {
  10. this.id = id;
  11. this.name = name;
  12. }
  13. public int id;
  14. public String name;
  15. @Override
  16. public String toString() {
  17. return "[id=" + id + ",name=" + name + "]";
  18. }
  19. }
  20. public static void main(String[] args) throws InterruptedException {
  21. User u = new User(1, "geym");
  22. SoftReference<User> userSoftRef = new SoftReference<User>(u);
  23. u = null;
  24. System.out.println(userSoftRef.get());
  25. System.gc();
  26. System.out.println("After GC:");
  27. System.out.println(userSoftRef.get());
  28. byte[] b = new byte[1024 * 500 * 2];
  29. System.gc();
  30. Thread.sleep(1000);
  31. System.out.println(userSoftRef.get());
  32. }
  33. }

使用参数-Xmx10m 运行。我使用的是JDK9

  1. [id=1,name=geym] (第一次从软引用中获取数据)
  2. After GC:
  3. [id=1,name=geym] (GC没有清除软引用)
  4. null (由于内存紧张,软引用被清除)

GC未必会回收软引用的对象,但是,当内存资源紧张时,软引用对象会被回收,所以软引用对象不会引起内存溢出。

4.3.4 弱引用——发现即回收

**
弱引用是一种以软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间使用情况如何,都会将对象进行回收。但是由于垃圾回收器的线程通常优先级很低,因此,并不一定能很快地发现持有弱引用的对象。在这种情况下,弱引用对象可以存在较长的时间。一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册的引用队列中。

  1. /**
  2. * 不管当前内存空间足够与否,都会回收它的内存
  3. * @author geym
  4. *
  5. */
  6. public class WeakRef {
  7. public static class User{
  8. public User(int id,String name){
  9. this.id=id;
  10. this.name=name;
  11. }
  12. public int id;
  13. public String name;
  14. @Override
  15. public String toString(){
  16. return "[id="+String.valueOf(id)+",name="+name+"]";
  17. }
  18. }
  19. public static void main(String[] args) {
  20. User u=new User(1,"geym");
  21. WeakReference<User> userWeakRef = new WeakReference<User>(u);
  22. u=null;
  23. System.out.println(userWeakRef.get());
  24. System.gc();
  25. //不管当前内存空间足够与否,都会回收它的内存
  26. System.out.println("After GC:");
  27. System.out.println(userWeakRef.get());
  28. }
  29. }