zoukankan      html  css  js  c++  java
  • 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四种引用类型

  • 相关阅读:
    setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key
    Kinect 骨骼追踪数据的处理方法
    了解与建设有中国特色的Android M&N(Android6.0和7.0新特性分析)
    【计算机视觉】深度相机(一)--TOF总结
    A million requests per second with Python
    buf.fill()
    buf.slice()
    buf.toJSON()
    buf.toString()
    Buffer.compare()
  • 原文地址:https://www.cnblogs.com/kfcuj/p/14646416.html
Copyright © 2011-2022 走看看