1. 如何判断对象可以回收

1.1. 引用计数法

在对象中添加一个引用计数器,每当有一个地方引用它是,计数器值就加一;但引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能在被使用的。这种算法简单,高效,但是不能处理相互引用的情况,比如下图所示:
image.png
A对象与B对象相互引用,但是除此之外没有任何对象指向A和B,这种情况引用计数法就不能处理了。

1.2. 可达性分析算法

  • Java虚拟机中的垃圾回收器采用可达性分析算法来探索所有存活的对象。
  • 扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,找不到,表示可以回收
  • 那些对象可以作为GC Root?(通过工具Memory Analyzer(mat)**)

    1.2.1. 使用mat来分析可达性

    测试代码: ```bash import java.io.IOException; import java.util.ArrayList;

/**

  • 演示GC Roots
  • @author : gnehcgnaw
  • @since : 2020/4/11 18:09 */ public class Demo1 { public static void main(String[] args) throws IOException {

    1. ArrayList<String> arrayList = new ArrayList<>() ;
    2. arrayList.add("a");
    3. arrayList.add("b");
    4. System.out.printf("1");
    5. System.in.read();
    6. arrayList = null ;
    7. System.out.printf("2");
    8. System.in.read();
    9. System.out.printf("end");

    } } ```

  1. 使用JPS查看Java进程

    1. chapter2 git:(master) jps
    2. 20049
    3. 20137 RemoteMavenServer36
    4. 20254 Launcher
    5. 20255 Demo1
    6. 20367 Jps
  2. 使用jmp工具dump堆,(分两次dump,第一次是程序执行到输出1的时候,第二次是程序执行到输出2的时候)

    1. # 控制台输出1的时候
    2. jmap -dump:format=b,live,file="/Users/gnehcgnaw/Desktop/demo1_1.bin" 20255
    3. # 控制台输出2的时候
    4. jmap -dump:format=b,live,file="/Users/gnehcgnaw/Desktop/demo1_2.bin" 20255
  3. 将文件导入到mat工具中

image.png

  1. 点击图示中的GC Root,具体如下所示:

image.png

  • 展开之后文件1和文件2的对比图:

image.png
总结:
在Java技术体系里面,固定作为GC Roots的对象包括如下几种:

  1. 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等;
  2. 在方法区中类静态属性引用的对象,比如Java类的引用类型静态变量;
  3. 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用;
  4. 在本地方法栈中的JNI(即通常说的Native方法)引用对象;
  5. Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如:NullPointException、OutOfMemoryError)等,还有系统类加载器;
  6. 所有被同步锁(synchronized关键字)持有的对象。
  7. 反应Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

    1.3. 四种引用

    无论是通过引用计数法判断对象的引用数量,还是通过可达性分析算法判断对象是否引用链可达,判断对象是否存活都和“引用”离不开关系。
    image.png

    1.3.1. 强引用(Strongly Reference)

    强引用是最传统的“引用”定义,是指在程序代码中普遍存在的引用赋值,即类似“Object object = new Object()”,这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象,只有都断开的情况下才会被垃圾回收机制回收,例如下图所示:【此时的A1对象就可以被回收了。】
    image.png
    A1对象被回收之后:
    image.png
    总结:
  • 只有所有的GC Roots对象都不通过【强引用】引用该对象,该对象才能被垃圾回收。

    1.3.2. 软引用(Soft Reference)和弱引用(Weak Reference)

    软引用和弱引用相差不大,故而放在一起来说明。
    只要A2和A3对象没有被直接的强引用对象B引用所引用,那么它们当垃圾回收时都可能被回收。
    举例:

  • 强引用C对象经过软引用或者弱引用对象间接的引用了A2和A3对象,但是又被强引用对象B所引用,那么A2和A3是不能被垃圾回收的;

  • 假如如下图所示B对象和A2对象直接的引用对象断开,在以下情况下A2对象是有可能被回收的:
    • 当垃圾回收时,并且回收之后内存不够时,就会把软引用所引用的这个对象释放掉(JVM认为软引用引用的对象不重要,但内存不足时我需要把它占用的空间腾出来,给更有用的对象去使用)。
  • B与A3没有强引用,在垃圾回收时,不管内存够不够都会回收掉弱引用引用的A3对象。这也是软引用和弱引用的区别所在。

image.png
软、弱引用还可以配合一个引用队列来一起工作:

  • 当软引用引用的对象被垃圾回收时,软引用自身也是一个对象,如果当时配合了引用队列的话,会进入到引用队列中;
  • 当弱引用引用的对象也被垃圾回收时,弱引用如果但是配合了引用队列的话,也会进入到引用队列中。

如下图所示:
image.png
那为什么要这么做呢?(即为什么要将软引用对象和弱引用对象放在引用队列中呢?)
就是因为软引用也好,弱引用也好,它们自身也要占用一定的内存,如果你需要对它们自身占用的内存做释放,那么就需要使用引用队列来找到它俩,对它们做进一步的处理,比如说它俩还被强引用所引用,只需要一次遍历
把它俩占用的内存释放掉。
image.png
总结:

  • 软引用
    • 仅当软引用引用该对象是,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象;
    • 可以配合引用队列来释放软引用自身。
  • 弱引用

    • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象;
    • 可以配合引用队列来释放弱引用自身。

      1.3.4. 虚引用(Phantom Reference)和终结器引用(FinalReference)

      虚引用和终结器引用与软、弱引用不同,软、弱引用既可以配合引用队列来使用,也可以不配合引用队列来使用,而虚引用和终结器应用必须配合引用队列来使用。也就是说当虚引用对象创建的时候就要关联一个引用队列,当终结器引用对象创建的时候它也就要关联一个引用队列。
      那它们是怎么工作的呢?

      1.3.4.1. 虚引用

      举例:ByteBuffer申请直接内存
      2020-04-11_21-07-50 (1).gif

      1.3.4.2. 终结器引用(不推荐使用)

      image.png
      所有的Java对象,都会继承Object父类,而Object父类中有一个finallize()方法,当A4这个对象重写了终结方法,并且B对象没有强引用A4,那么A4就会被垃圾回收,那终结方法什么时候被调用呢?**它就是靠终结器引用对象达到调用finallize()方法的。
      总结:**
  • 虚引用

    • 必须配合引用队列使用,主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存。
  • 终结器引用
    • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(**被引用对象暂时没有被回收**),再由Finalizer线程通过终结器引用找到被其引用对象并调用它的finalize方法,第二次GC时才能回收被引用对象。

      1.3.5. 应用示例

      1.3.5.1. 软引用示例

  1. 强引用示例 ```bash import java.util.ArrayList;

/**

  • 强引用示例
  • -Xmx20m -XX:+PrintGCDetails -verbose:gc
  • @author : gnehcgnaw
  • @since : 2020/4/12 11:39 / public class Demo2 { private static final int _4MB = 10241024*4 ; public static void main(String[] args) {

    1. int j = 0;
    2. ArrayList<byte[]> arrayList = new ArrayList<>() ;
    3. try {
    4. for (int i = 0; i < 100 ; i++) {
    5. arrayList.add(new byte[_4MB]);
    6. j++ ;
    7. }
    8. }catch (Throwable throwable){
    9. throwable.printStackTrace();
    10. }finally {
    11. System.out.println(j);
    12. }

    } }

运行结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/285619/1586674960471-2e7efbaa-8eef-4a59-8c03-51b88358e3ce.png#align=left&display=inline&height=172&margin=%5Bobject%20Object%5D&name=image.png&originHeight=411&originWidth=1785&size=106316&status=done&style=shadow&width=746)<br />因为空间不够进行了年轻代的空间回收,但是还是不够,就进行了Full GC,但是最后还是产生了:堆溢出。 

2. 软引用示例
```bash
import java.lang.ref.SoftReference;
import java.util.ArrayList;

/**
 * 软引用示例
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 * @author : <a href="mailto:gnehcgnaw@gmail.com">gnehcgnaw</a>
 * @since : 2020/4/12 11:39
 */
public class Demo3 {
    private static  final int _4MB = 1024*1024*4 ;
    public static void main(String[] args) {
        int j = 0;

        ArrayList<SoftReference<byte[]>> softReferenceArrayList = new ArrayList<>();
        try {
            for (int i = 0; i < 10 ; i++) {
                softReferenceArrayList.add(new SoftReference<>(new byte[_4MB]));
                j++ ;
            }
            for (SoftReference softReference :softReferenceArrayList) {
                System.out.println(softReference.get());
            }

        }catch (Throwable throwable){
            throwable.printStackTrace();
        }finally {
            System.out.println(j);
        }

    }
}

运行结果:
image.png
程序没有报错,但是发现list集合中有null值,这是因为当空间不足的时候首先会进行年轻代的GC,然后是Full GC,这时会将软引用指向的空间进行清理,发现最后只存放了2个元素,但其实软引用对象本身也占用空间,那这部分空间如何处理呢?需要软引用和引用队列相结合的方式。

  1. 软引用和引用队列相结合 ```bash import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.ArrayList;

/**

  • 软引用配合引用队列
  • -Xmx20m -XX:+PrintGCDetails -verbose:gc
  • @author : gnehcgnaw
  • @since : 2020/4/12 13:03 / public class Demo4 { private static final int _4MB = 10241024*4 ;

    public static void main(String[] args) {

     int j = 0 ;
     ArrayList<SoftReference<byte[]>> softReferenceArrayList = null ;
     try{
         softReferenceArrayList = new ArrayList<>();
         //引用队列的泛型要与软引用保持一直
         ReferenceQueue<byte[]> softReferenceReferenceQueue = new ReferenceQueue<>();
    
         for (int i = 0; i <10; i++) {
             //关联引用队列,当软引用关联的byte[]被回收时,软引用自己会加入到queue中去。
             SoftReference<byte[]> softReference = new SoftReference<>(new byte[_4MB],softReferenceReferenceQueue);
             softReferenceArrayList.add(softReference);
             j++ ;
         }
         //从队列中出对一个软引用
         Reference<? extends byte[]> reference = softReferenceReferenceQueue.poll();
         while (reference!=null){
             //这里的reference在list中都是无效的,故而需要从list中清理掉
             softReferenceArrayList.remove(reference);
             //出对下一个
             reference = softReferenceReferenceQueue.poll();
         }
         for (SoftReference softReference : softReferenceArrayList){
             System.out.println(softReference.get());
         }
     }catch (Throwable throwable){
         throwable.printStackTrace();
     }finally {
         System.out.println("j = " + j);
         System.out.println("softReferenceArrayList's size is "+ softReferenceArrayList.size());
     }
    
}

}

运行结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/285619/1586676803906-4255add8-af00-4c42-bebf-c5fee6ad87c0.png#align=left&display=inline&height=241&margin=%5Bobject%20Object%5D&name=image.png&originHeight=554&originWidth=1715&size=139991&status=done&style=shadow&width=746)
<a name="zze2h"></a>
#### 1.3.5.2. 弱引用示例
```bash
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;

/**
 * -Xmx10m -XX:+PrintGCDetails -verbose:gc
 * 弱引用示例
 * @author : <a href="mailto:gnehcgnaw@gmail.com">gnehcgnaw</a>
 * @since : 2020/4/12 15:43
 */
public class Demo5 {
    private final static int _4MB = 1024*1024*4 ;
    public static void main(String[] args) {
        try{
            ArrayList<WeakReference<byte[]>> weakReferenceArrayList = new ArrayList<>() ;
            ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>() ;
            for (int i = 0; i <10 ; i++) {
                WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB], referenceQueue);
                weakReferenceArrayList.add(weakReference);
            }
            Reference<? extends byte[]> poll = referenceQueue.poll();
            while (poll!=null){
                weakReferenceArrayList.remove(poll);
                poll = referenceQueue.poll() ;
            }

            for (WeakReference weakReference : weakReferenceArrayList){
                System.out.println(weakReference.get());
            }
        }catch (Exception exception){
            exception.printStackTrace();
        }

    }
}
  • 【待解决】**将内存调整到很小还是会发生内存溢出,原因是软引用对象本身也占用空间,那这种问题怎么解决呢?**

image.png