02 Java的引用类型以及应用场景

1 JVM如何判断对象可以回收?

1-1 概述

1)采用引用计数法,引用为0的对象就是可以回收的对象。

  • 存在问题:循环引用的问题需要去特殊考虑。

2)可达性分析算法(JVM实际采用的方法)

  • 基本思想:需要确定一系列根对象(根对象肯定不能被垃圾回收),然后对空间进行扫描,判断每一个对象是否直接或者间接被根对象引用,如果是的话,那么这个对象不能被垃圾回收。
方法论:可达性分析算法中哪些对象可以作为根对象?

结合JMAP与内存分析工具

jmap -dump:format=b,file=filename pid 
jmap -dump:format=b,live,file=file2.bin 7640   // live确保dump之前进行一次垃圾回收

实例

package part2;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class test1 {
    public static void main(String[] args) throws IOException {
        List<Object> list1 = new ArrayList<>();
        list1.add("a");
        list1.add("b");
        System.out.println(1);
        System.in.read();
        list1 = null;
        System.out.println(2);
        System.in.read();
        System.out.println("end");
   }
}
  • 上面代码中在打印1和2之后分别用jmap命令保存内存状态,获得2个bin文件。
jmap -dump:format=b,live,file=file1.bin 16180
jmap -dump:format=b,live,file=file2.bin 16180

使用MAP工具打开file1.bin

1)使用GC root工具

2)可以看到该工具将root类分为四种类型:

  • system class:系统类都是核心类,虚拟机运行过程中核心对象
  • JNI(Java Native Interface) Global:操作系统资源调用时所使用的Java类对象
  • Busy Montor: 锁对象(锁对象作为root类可以保证由于加锁被阻塞的类不会被垃圾回收
  • Thread:线程中的对象

3)main线程中可以看到当前正在被引用的ArrayList<>()对象 。

List<Object> list1 = new ArrayList<>();

4)打开file2.bin,可以发现ArrayList<>()对象 不在主线程中,已经被回收了


问题:哪些对象是root对象?

作为 GC Roots 的对象包括下面几种:
1)虚拟机栈(栈帧中的本地变量表)中引用的对象;
2)各个线程调用方法堆栈中使用到的参数、局部变量、临时变量等。
3)方法区中类静态属性引用的对象;java 类的引用类型静态变量。
4)常量引用的对象;比如:字符串常量池里的引用。
5) 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象。
6) 所有被同步锁(synchronized 关键)持有的对象。(非重点)
7) JVM 的内部引用(class 对象、异常对象 NullPointException、OutofMemoryError,系统类加载器)。(非重点)
8)JVM 内部的 JMXBean、JVMTI 中注册的回调、本地代码缓存等(非重点)
9)JVM 实现中的“临时性”对象,跨代引用的对象

1-2 Java的引用类型(重要知识点)

1)强引用(上图中的实现都是强引用)

  • 特点
    • 沿着GC root对象的引用链能够找到该对象。上图A1对象与C对象之间就是强引用
    • 强引用对象不会被垃圾回收

2)软引用下图中的A2对象就是软引用的对象)(SoftReference )

软引用特点

  • 软引用对象在没有其他对象引用的情况下,内存不足时,JVM会垃圾回收这种类型的对象。

3)弱引用上图中的A3对象就是软引用)(WeakReference )

特点:

  • 只要发生垃圾回收,弱引用对象都会被回收
软引用与弱引用对象的区别?

**共同点: **软引用对象如果没有被强引用都会被垃圾回收。可以配合引用队列,也可以不配合引用队列使用。

不同点: 回收的时机存在差异,弱引用只要发生GC,必定被回收,软引用在内存不足的时候,GC时才会回收。

注意:

  • 软弱引用的对象被垃圾回收后,引用本身会进入到引用队列,由于引用本身也占用空间,如果像释放

引用所占的空间,那么就通过引用队列找到哪些引用并释放


虚引用与终结器引用的特点:这两种类型的引用的必须关联引用队列

4)虚引用(PhantomReference )

典型应用:当虚引用的对象被垃圾回收的时候,虚引用本身就会被放入引用队列,会有一个专门的线程扫描引用队列,调用虚引用关联的方法。

实例:比如直接内存由于不受JVM垃圾回收管理,因此需要通过虚引用的方式去对内存进行释放,具体的流程就是

虚引用cleaner存储直接内存的地址,当虚引用的的对象bytebuffer被垃圾回收后会进入队列,该队列会有线程扫描找到虚引用,调用unsafe的本地方法释放直接内存。

特点:如果一个对象仅持有虚引用,那它就和没有任何引用一样。


5)终结器引用(FinalReference )


特点

  • 终结器引用配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize方法,第二次 GC 时才能回收被引用对象

注意:Finalizer 线程的优先级非常低,导致垃圾回收并不太及时,因此终结器引用很少使用到。

五种引用的总结
引用名称 与垃圾回收的关系 备注
强引用 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收 最常见的应引用
软引用 1)仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象。2)可以配合引用队列来释放软引用自身 注意软引用对象垃圾回收的时机, 常用来实现缓存技术,比如网页缓存,图片缓存等
弱引用 1)仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象。2)可以配合引用队列来释放弱引用自身 注意与软引用区分
虚引用 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存 主要用于直接内存释放
终结器引用 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize方法,第二次 GC 时才能回收被引用对象 垃圾回收效率低

1-3 软引用的应用场景

场景下的问题:一些大体积资源加载到内存中进行显示,但是这些资源又不是特别重要,如果采用强引用的方式,由于这些大体积的资源无法及时得到释放,很容易造成heap space溢出。

准备:通过JVM参数限制堆内存大小为20M

package part2;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/*-Xmx20m 设置堆内存大小为20M,每个byte数组大小为4M
* */
public class test2 {
    private static final int _4MB = 4 * 1024 * 1024;
    public static void main(String[] args) throws IOException {
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }
        System.in.read();
    }
}

执行结果

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at part2.test2.main(test2.java:19)

溢出原因:堆内存资源只有20M,但是5个byte数组资源就占用了20M,造成堆内存不足,又由于这些byte数组都被根对象强引用,所以无法进行垃圾回收,造成堆内存溢出exception.

软引用的应用(内存敏感的应用有益处)
package part2;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/**
 * 演示软引用
 *  -XX:+PrintGCDetails -verbose:gc (打印垃圾回收的详细信息)
 */
/*-Xmx20m  (设置堆内存大小为20M,每个byte数组大小为4M)
*
* */
public class test2 {
    private static final int _4MB = 4 * 1024 * 1024;
    public static void main(String[] args) throws IOException {
        // list --> SoftReference --> byte[]
        /*让list先引用软引用,然后软引用在引用byte数组*/
        List<SoftReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());

        }
        System.out.println("循环结束:" + list.size());
        for (SoftReference<byte[]> ref : list) {
            System.out.println(ref.get());
        }
    }
}

执行结果

上述代码中通过弱引用去引用byte数组,这样当内存不足的时候发生垃圾回收,会将之前的弱引用对象全部会后,从结果中可以看到垃圾回收发生在第5个byte数组new出来之前

[B@45ee12a7
1
[B@330bedb4
2
[B@2503dbd3
3
[B@4b67cf4d
4
[B@7ea987ac
5
循环结束:5
null
null
null
null
[B@7ea987ac

第二次循环,发现前面四个byte数组已经被垃圾回收了

设置详细的垃圾回收信息打印

执行结果

[B@45ee12a7
1
[B@330bedb4
2
[B@2503dbd3
3                     //  内存不足了,第一次对YoungGen进行垃圾回收
[GC (Allocation Failure) [PSYoungGen: 2338K->488K(6144K)] 14626K->13019K(19968K), 0.0071512 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[B@4b67cf4d
4                    //  第二次对YoungGen进行垃圾回收
[GC (Allocation Failure) --[PSYoungGen: 4696K->4696K(6144K)] 17228K->17244K(19968K), 0.0012066 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
                     // 第三次触发Full GC,可以看到回收后的内存占用并没有下降多少。
[Full GC (Ergonomics) [PSYoungGen: 4696K->4560K(6144K)] [ParOldGen: 12547K->12497K(13824K)] 17244K->17058K(19968K), [Metaspace: 3537K->3537K(1056768K)], 0.0057532 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
                    // 内存依旧不足,造成内存分配失败
[GC (Allocation Failure) --[PSYoungGen: 4560K->4560K(6144K)] 17058K->17082K(19968K), 0.0007331 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
                   // 第四次触发GC,这个时候之前的软引用对象进行了回收,可以看到内存占用下降了很多
[Full GC (Allocation Failure) [PSYoungGen: 4560K->0K(6144K)] [ParOldGen: 12521K->655K(8704K)] 17082K->655K(14848K), [Metaspace: 3537K->3537K(1056768K)], 0.0079717 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[B@7ea987ac
5
循环结束:5
null
null
null
null
[B@7ea987ac
Heap
 PSYoungGen      total 6144K, used 4376K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
  eden space 5632K, 77% used [0x00000000ff980000,0x00000000ffdc6288,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 8704K, used 655K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)
  object space 8704K, 7% used [0x00000000fec00000,0x00000000feca3e80,0x00000000ff480000)
 Metaspace       used 3544K, capacity 4540K, committed 4864K, reserved 1056768K
  class space    used 396K, capacity 428K, committed 512K, reserved 1048576K

总结:从上面过程中可以看到,软引用的特点

  • 垃圾回收的时机是堆内存分配失败,不足的时候,才会被真正回收
  • 软引用与弱引用可以应用于一些对于内存比较敏感的场景

1-4 使用引用队列清理无用的软引用

package part2;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.*;
/*虚拟机环境配置如下:
 *-Xmx20m 设置堆内存大小为20M,每个byte数组大小为4M
 * */
public class test3 {
    private static final int _4MB = 4 * 1024 * 1024;
    public static void main(String[] args) throws IOException {
        // 引用队列
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
        List<SoftReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            // 将软引用对象与引用队列进行关联
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB],queue);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());

        }
        System.out.println("循环结束:" + list.size());
        Reference<? extends byte[]> poll = queue.poll();
        // 从引用队列中确定哪些引用没有作用了并进行移除
        while(poll != null){
            list.remove(poll);
            poll = queue.poll();
        }
        System.out.println("=================");
        // 链表中无用的引用在上一步中已经被移除
        for (SoftReference<byte[]> ref : list) {
            System.out.println(ref.get());
        }
    }
}

执行结果

通过配合引用队列,移除没有作用的软引用对象。

[B@45ee12a7
1
[B@330bedb4
2
[B@2503dbd3
3
[B@4b67cf4d
4
[B@7ea987ac
5
循环结束:5
=================
[B@7ea987ac

1-5 弱引用的应用

package part2;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.*;
/*虚拟机环境配置如下:
 *-Xmx20m -XX:+PrintGCDetails -verbose:gc设置堆内存大小为20M,每个byte数组大小为4M
 * */
public class test4 {
    private static final int _4MB = 4 * 1024 * 1024;
    public static void main(String[] args) throws IOException {
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            // 将软引用对象与引用队列进行关联
            WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());
        }
        System.out.println("循环结束:" + list.size());
        System.out.println("========================");
        for (WeakReference<byte[]> ref : list) {
            System.out.println(ref.get());
        }
    }
}

执行结果

  • 弱引用对象会被垃圾回收
[B@45ee12a7
1
[B@330bedb4
2
[B@2503dbd3
3
[GC (Allocation Failure) [PSYoungGen: 2338K->488K(6144K)] 14626K->13023K(19968K), 0.0018467 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@4b67cf4d
4
[GC (Allocation Failure) [PSYoungGen: 4696K->488K(6144K)] 17232K->13031K(19968K), 0.0010357 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@7ea987ac
5
循环结束:5
========================
[B@45ee12a7
[B@330bedb4
[B@2503dbd3
null
[B@7ea987ac
Heap
 PSYoungGen      total 6144K, used 4752K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
  eden space 5632K, 75% used [0x00000000ff980000,0x00000000ffdaa018,0x00000000fff00000)
  from space 512K, 95% used [0x00000000fff80000,0x00000000ffffa040,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 13824K, used 12543K [0x00000000fec00000, 0x00000000ff980000, 0x00000000ff980000)
  object space 13824K, 90% used [0x00000000fec00000,0x00000000ff83fdb0,0x00000000ff980000)
 Metaspace       used 3544K, capacity 4540K, committed 4864K, reserved 1056768K
  class space    used 396K, capacity 428K, committed 512K, reserved 1048576K

参考资料

01 JVM基础课程
02 Java四种引用类型

posted @ 2021-04-12 08:19  狗星  阅读(328)  评论(0编辑  收藏  举报
/* 返回顶部代码 */ TOP