JVM虚拟机中如何判断对象可以回收

发布于:2023-01-04 ⋅ 阅读:(365) ⋅ 点赞:(0)

如何判断对象可以回收

一. 引用计数法

指只要一个对象被其他变量所引用,那就让对象这个计数+1,如果引用两次则计数+2,如果某个变量不在被引用那让它计数减一,当这个对象引用计数变为0时,那么无法被引用而被回收

  • 存在的弊端:循环引用的问题(A对象引用B对象,B对象引用A对象,两者一直互相引用,从而导致内存泄漏)
    会存在内存泄漏
  • 措施:为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的 “GC roots”
    对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。
  • 注意:不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。

在这里插入图片描述

二. 可达性分析算法

  • 定义:是Java虚拟机用来判断对象是否为垃圾的算法称为可达性分析算法

    • Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
    • 为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”
      对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的
  • 判断条件:扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以回收;( 确定根对象(肯定不能被垃圾回收的对象),每个对象是不是被根对象直接或间接的引用,如果是,则将来不能作为垃圾回收。反之不是,可以作为垃圾回收 )

    • 例如葡萄,用手抓的葡萄根上的葡萄可以吃,而掉了落下来的葡萄则要扔到垃圾桶里
  • 具体操作:
    牵扯的代码:

package jvm1;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

    /**
     * 演示GC Roots
     */
    public class Demo12 {

        public static void main(String[] args) throws InterruptedException, 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...");
        }
    }



先单独说下jmap
jmap:导出内存映像文件&内存使用情况
jmap( JVM Memory Map):作用一方面是获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息类加载信息等开发人员可以在控制台中输入命令“jmap -help”查阅jmap工具的具体使用方式和一些标准选项配置
在这里插入图片描述

  • 一般来说,使用jmap指令生成dump文件的操作算得上是最常用 的jmap命令之一,将堆中所有 存活对象导出至一个文件之中。 Heap Dump又叫做堆存储文件,指一个Java进程在某个时间点 的内存快照。 Heap Dump在触 发内存快照的时候会保存此刻的信息如下
    • All Objects
      Class, fields, primitive values and references
      All Classes
      Classloader, name, super class, static fields
      Garbage Collection Roots
      Objects defined to be reachable by the JVM
      Thread Stacks and Local Variables
      The call-stacks of threads at the moment of the snapshot, and per-frame
      information about local objects
      说明
      1. 通常在写 Heap Dump文件前会触发一次Full GC,所以 heap dump文件里保存的都是 Fu116C后留下的对象信息(这个针对的是自动的方式)
      2. 由于生成dump文件比较耗时,因此大家需要耐心等待,尤其是大内存镜像生成dump文件则需要耗费更长的时间来完成
      3. -dump:[options]主要有:
        live 只dump存活的对象,如果不加则会dump所有对象
        format=b 表示以二进制格式
        file=filepath 输出到某个文件中
        把java堆中的对象dump到本地文件,然后使用第三方工具进行分析,比如如MAT(本次主讲工具),JProfile,IBM的分析工具等
  1. 操作1
    • jps 查看进程id,直接在程序运行时,直接jps
    • jmap 某个时刻堆内存的情况,jdk8之前使用 jmap -heap pid,但是jdk8之后必须使用jhsdb jmap --heap --pid *** ,不能使用 jmap -heap pid了,否则会报Error: -heap option used错误。
    • jconsole 动态查看堆内存情况,直接jconsole ,然后弹出可视化窗口。
      注意:其中1+2 要结合使用,且是静态的查看;3可以单独使用,动态查看
  2. 操作2
    • jps 查看进程id,直接在程序运行时,直接jps
    • jmap 某个时刻堆内存的情况,jdk8之前使用 jmap -heap pid,但是jdk8之后必须使用jhsdb jmap --heap --pid *** ,不能使用 jmap -heap pid了,否则会报Error: -heap option used错误。
    • MAT工具在这里插入图片描述
  • 操作2的具体实施:

在这里插入图片描述
2. 打开MAT工具
在这里插入图片描述
3. 设置选项
在这里插入图片描述
4. 查看gc Roots
在这里插入图片描述
5. 查看内容
在这里插入图片描述

  • *注意内容
    • 1.bin在这里插入图片描述
    • 2.bin
      在这里插入图片描述

三. 常见的5种引用

1. 强引用

定义:在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之一。
特点:只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收

分析1:
在这里插入图片描述
分析2:
在这里插入图片描述

  • 在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之一

2. 软弱引用

软引用定义软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它
弱引用定义弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象
来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。
不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。
软引用(SoftReference)特点

  • 仅有软引用引用该对象时,内存仍不足时触发垃圾回收,回收软引用对象
  • 可以配合引用队列来释放软引用自身

弱引用(WeakReference)特点

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

分析1:
在这里插入图片描述
分析2:
在这里插入图片描述

    1. 当没有其他强引用直接引用时候,触发垃圾回收时,并且发现内存不够时,那么就会把引用引用的A2对象被垃圾回收掉
    2. 当没有其他强引用直接引用时候,只要触发垃圾回收时,不管内存是否充足,都会把引用引用的A3对象被垃圾回收掉

    分析3:
    在这里插入图片描述

    1. 当我的软引用调用的A2对象被回收掉时,那么软引用自身也是一个对象,如果创建时分配引用队列时,那么就会进入引用队列,但是也可以不配合引用队列的使用
    2. 软引用也好,弱引用也好,自身也要占用内存,如果要释放软和弱引用,则需要引用队列找到并遍历它两才能释放

① 软引用的应用

案例:假如读取图片资源,或者查找文件等,会出现查找途中占用内存过程大,并且内存容易溢出怎么办?

代码演示:

分析1:不需要软引用(SoftReference)情况下

package jvm1;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Demo13 {


    /**
     * -Xmx20m
     */

    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]);//一共执行5次
        }
    }
}



在这里插入图片描述

分析2:需要软引用(SoftReference)情况下(例如:内存敏感,或者文件不太重要情况下,空间紧张时)
-XX:+PrintGCDetails -verbose:gc 打印我们垃圾回收的详细参数

package jvm1;

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

public class Demo13 {


    /**
     * 演示软引用
     * -Xmx20m -XX:+PrintGCDetails -verbose:gc
     */

    private static final int _4MB = 4 * 1024 * 1024;


    public static void main(String[] args) throws IOException {
        soft();
    }

    public static void soft() {
        // 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());
        }
    }
}



  • 分析1:
    在这里插入图片描述

  • 分析2:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

② 软引用的引用队列

演示的代码:

package jvm1;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

public class Demo14 {
    /**
     * 演示软引用, 配合引用队列
     */
        private static final int _4MB = 4 * 1024 * 1024;

        public static void main(String[] args) {
            List<SoftReference<byte[]>> list = new ArrayList<>();

            // 引用队列
            ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

            for (int i = 0; i < 5; i++) {
                // 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
                SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
                System.out.println(ref.get());
                list.add(ref);
                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[]> reference : list) {
                System.out.println(reference.get());
            }

        }
    }

在这里插入图片描述

输出的内容:
[B@2812cbfa
1
[B@2acf57e3
2
[B@506e6d5e
3
[B@96532d6
4
[B@3796751b
5

===========================

[B@2812cbfa
[B@2acf57e3
[B@506e6d5e
[B@96532d6
[B@3796751b

③ 弱引用应用

WeakReference弱引用实例

演示代码:

package jvm1;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class Demo15 {

    /**
     * 演示弱引用
     * -Xmx20m -XX:+PrintGCDetails -verbose:gc
     */
        private static final int _4MB = 4 * 1024 * 1024;

        public static void main(String[] args) {
            //  list --> WeakReference --> byte[]
            List<WeakReference<byte[]>> list = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
                list.add(ref);
                for (WeakReference<byte[]> w : list) {
                    System.out.print(w.get()+" ");
                }
                System.out.println();

            }
            System.out.println("循环结束:" + list.size());
        }
    }


  • 分析1:
    当我for循环6次时
    在这里插入图片描述
  • 分析2:
    当我for循环10次时
    在这里插入图片描述

3. 虚终引用

虚引用定义:虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态

当我的虚终引用调用的ByteBuffer、直接内存和A4对象被回收掉时,那么虚终引用自身也是一个对象必须配合引用队列使用,当虚终引用创建对象时都会需要关联引用队列==

  1. 虚引用(PhantomReference)
    特点
    • 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存

分析1:
在这里插入图片描述
分析2:
在这里插入图片描述
分析3:
在这里插入图片描述

  • 将来ByteBuffer没有再被强引用,那么自己可以垃圾回收掉,但是他分配的直接内存并不能被我们的Java垃圾回收所管理,所以让ByteBuffer被回收时,让虚引用对象进入引用队列,那么就会由ReferenceHandler 线程定时的去找看有没有新入队的cleaner,如果有就会调动Cleaner 的 clean 方法,clean方法就会根据前边的直接内存地址,就会调用Usafe对象中的freeMemory 来释放直接内存,这样保证了直接内存不会泄漏
  1. 终结器引用(FinalReference)
    特点
    • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象
      暂时没有被回收
      ),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize
      方法,第二次 GC 时才能回收被引用对象

所以Java对象都会继承Object父类,并且父类里面有finallize()终结方法。当我重写了finallize()终结方法,并且没有强引用时就可以看下一步情况垃圾回收
分析1:
在这里插入图片描述
注意:finallize()方法工作效率很低,第一次垃圾回收时,还不能真正的垃圾回收掉,先让终结器入引用队列后,由于处理的线程优先级低,被执行的机会很少,可能会造成finallize()方法迟迟不能调用,内存迟迟不能释放,因为太复杂了,所以不推荐finallize()方法释放资源

本文含有隐藏内容,请 开通VIP 后查看