美文网首页
# JVM 垃圾回收-01 可达性分析与强弱软虚引用详解

# JVM 垃圾回收-01 可达性分析与强弱软虚引用详解

作者: 丿易小易 | 来源:发表于2020-12-10 13:30 被阅读0次

    可达型分析

    可达性分析的理论

    基本思路:
    通过一系列GC Roots的根对象作为起始节点集, 根据引用关系向下搜索, 
    搜索过程中走过的路径称为引用链,所有在这个引用链上的对象都是可达对象,
    而其他的没有与GC Roots根对象关联的单独存在的引用链上的对象则为不可达对象
    

    图示:


    image
    在内存中的场景:
    上图的 object1 中有字段object2 , object3 的引用, 
    当我们把object1的引用ref赋值为null, 
    那么object1就变为了 上图右侧的不可达对象的图例了..需要被gc回收掉了,如下图引用
    
    image
    代码实现举例子:
    
    
    @Data
    public class Test {
        public Test instance;
        public int i;
        public Test(int i) {
            this.i = i;
        }
    }
    
    
    public static void main(String[] args) {
        Test test0 = new Test(0);
        Test test1 = new Test(1);
        Test test2 = new Test(2);
        test0.instance=test1;
        test1=test2;
        System.out.println(test0.getInstance().getI());
    }
    

    执行结果 1
    为什么不是2呢,test1已经被test2赋值了呀!
    这是因为引用变量只能是指向某个对象的,而不能是指向引用的
    所以test0.instance 指向的是test1在堆中的对象,
    所以在后面改变了test1的指向时, 并没有影响之前的指向.

    
    public static void main(String[] args) {
        Test test0 = new Test(0);
        Test test1 = new Test(1);
        Test test2 = new Test(2);
        test0.instance=test1;
        test1.instance=test2;
        test0=null;
    }
    
    
    ps: test0=null则 test0的原映射对象是不在引用链上了,会被gc回收.
    且test0.instance的由上例子可知是引用的对象test1, 但这里不会回收test1,
    因为test1也自己定义了一个根对象, 所以test1还是在test1引用链上,但不在test0的引用链上了,
    如果将test1=null也这样设置,那么test1也将会被gc回收;
    

    问题:那么在平时的代码中我们要不要在使用后对象就将对象置空呢?
    这个问题要看 JVM 垃圾回收-判断对象是否可以回收 中的描述了

    可达性根枚举对象

    GC Roots的对象
    1. 虚拟机栈中的引用的对象            如:Object o = new Object(); o即为虚拟栈中的引用对象
    2. 方法区中类静态属性引用的对象       如:public static String static_str="111";  static_str 即为引用对象
    3. 方法区中常量引用的对象
    4. 本地方法栈中 JNI(native方法)引用的对象
    5. 被同步锁synchronize持有的对象
    
    ps:这里提到了很多引用,我们下面将详细描述引用的分类,引用之所以进行设定不同的分类, 
       是因为在内存中数据的要求是多样的,
       比如 我们希望在内存充足的情况下,保留这些类,但内存不足的时候,就进行回收(缓存机制)
    
    

    引用是如何分类

    强引用

    使用方法

    正常的写的java代码都是强引用99.999%
    Object o = new Object();
    
    这种就是强引用了,是不是在代码中随处可见,最亲切。 
    只要某个对象有强引用与之关联,这个对象永远不会被回收
    即使内存不足,JVM宁愿抛出OOM,也不会去回收
    

    回收的方法:

    o = null;
    

    示例:

    我们需要新写一个类,然后重写finalize方法
    
    public class Student {
        @Override
        protected void finalize() throws Throwable {
            System.out.println("Student 被回收了");
        }
    }
    
    public static void main(String[] args) {
            Student student = new Student();
            student = null;
            System.gc();
    }
    
    运行结果:
    Student 被回收了
    

    注释:finalize方法是当gc时,系统来调用该方法
    释放该对象在堆中占用的内存. 该方法在Object中

    软引用

    使用方法

    用java.lang.ref.SoftReference 进行包装
    SoftReference<Student>studentSoftReference=new SoftReference<Student>(new Student());
    

    回收方法

    gc自动处理
    当内存不足,会触发JVM的GC,如果GC后,内存还是不足,就会把软引用的包裹的对象给干掉
    也就是只有在内存不足,JVM才会回收该对象
    

    示例:

    修改idea的堆内存大小 -Xmx20m  最大堆内存为20m , 设置内存追踪 -XX:+PrintGCDetails
    
      SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]);
      System.out.println(softReference.get());
      System.gc();
      System.out.println(softReference.get());
      byte[] bytes = new byte[1024 * 1024 * 10];
      System.out.println(softReference.get());
      
      创建一个软引用对象,里面包裹了byte[],byte[]占用了10M,然后又创建了10Mbyte[]
      
    结果:
    
    [GC (Allocation Failure)  5632K->1393K(19968K), 0.0049815 secs]
    [B@763d9750
    [GC (System.gc())  16187K->12252K(19968K), 0.0014227 secs]
    [Full GC (System.gc())  12252K->11862K(19968K), 0.0101228 secs]
    [B@763d9750
    [GC (Allocation Failure)  12032K->11926K(19968K), 0.0004047 secs]
    [GC (Allocation Failure)  11926K->11926K(19968K), 0.0002842 secs]
    [Full GC (Allocation Failure)  11926K->11784K(19968K), 0.0049854 secs]
    [GC (Allocation Failure)  11784K->11784K(19968K), 0.0002981 secs]
    [Full GC (Allocation Failure)  11784K->1495K(16896K), 0.0066588 secs]
    null
     
    分析: 当gc回收时, 软 
    

    注释:一般是缓存使用

    弱引用

    使用方法

    java.lang.ref.WeakReference包装
    WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1024*1024*10]);
    System.out.println(weakReference.get());
    
    

    回收方式

    System.gc();
    不管内存是否足够,只要发生GC,都会被回收
    

    示例

    WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1]);
    System.out.println(weakReference.get());
    System.gc();
    System.out.println(weakReference.get());
    
    结果:
    [GC (Allocation Failure)  5632K->1410K(19968K), 0.0016663 secs]
    [B@763d9750
    [GC (System.gc())  5726K->2034K(19968K), 0.0015112 secs]
    [Full GC (System.gc())  2034K->1914K(19968K), 0.0113147 secs]
    null
    

    注释:弱引用在很多地方都有用到,比如ThreadLocal、WeakHashMap。

    虚引用

    使用方法:

     当发生GC,虚引用就会被回收,并且会把回收的通知放到ReferenceQueue中
     ReferenceQueue queue = new ReferenceQueue();
     PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);
     System.out.println(reference.get());
     
     作用时,当gc回收的时候会产生一条记录到ReferenceQueue中
    
    

    回收方式:

    System.gc() 自动回收
    

    示例:

    @Data
    @Builder
    public class Student {
        @Override
        protected void finalize() throws Throwable {
            System.out.println("Student 被回收了");
        }
    }
    
    
    public static void main(String[] args) {
            ReferenceQueue queue = new ReferenceQueue();
            List<byte[]> bytes = new ArrayList<>();
            PhantomReference<Student> reference = new PhantomReference<Student>(new Student(),queue);
            new Thread(() -> {
                for (int i = 0; i < 100;i++ ) {
                    bytes.add(new byte[1024 * 1024]);
                }
            }).start();
    
            new Thread(() -> {
                while (true) {
                    Reference poll = queue.poll();
                    if (poll != null) {
                        System.out.println("虚引用被回收了:" + poll);
                    }
                }
            }).start();
            Scanner scanner = new Scanner(System.in);
            scanner.hasNext();
        }
    
    结果: 
    
    [GC (Allocation Failure)  5632K->1372K(19968K), 0.0011861 secs]
    [GC (Allocation Failure)  7004K->2185K(19968K), 0.0017764 secs]
    [GC (Allocation Failure)  7205K->6394K(19968K), 0.0028557 secs]
    [GC (Allocation Failure)  11653K->11554K(19968K), 0.0027832 secs]
    [Full GC (Ergonomics)  11554K->11374K(19968K), 0.0134628 secs]
    [Full GC (Ergonomics)  16624K->16456K(19968K), 0.0055213 secs]
    [Full GC (Ergonomics)  18620K->18488K(19968K), 0.0098597 secs]
    [Full GC (Allocation Failure)  18488K->18402K(19968K), 0.0115410 secs]
    Student 被回收了
    [Full GC (Ergonomics)  18949K->17924K(19968K), 0.0106182 secs]
    Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
        at com.logistic.JVMTest.lambda$main$0(JVMTest.java:16)
        at com.logistic.JVMTest$$Lambda$1/747464370.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
    虚引用被回收了:java.lang.ref.PhantomReference@5eb93cd7
    
    

    注释:
    第一个线程往集合里面塞数据,随着数据越来越多,肯定会发生GC
    第二个线程死循环,从queue里面拿数据,如果拿出来的数据不是null,就打印出来

    相关文章

      网友评论

          本文标题:# JVM 垃圾回收-01 可达性分析与强弱软虚引用详解

          本文链接:https://www.haomeiwen.com/subject/oinlgktx.html