如何判断对象是否该被回收(引用计数法、可达性分析算法)

简介: 概述垃圾收集器需要完那些内存需要回收?什么时候回收?如何回收?

概述


垃圾收集器需要完成的三件事

那些内存需要回收?

什么时候回收?

如何回收?

那些需要进行GC

程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程灭而灭,栈中的栈帧随着方法的进入和退出有条不紊的进行出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知(尽管在运行期间会由即时编译器进行一些优化,但在基于概念模型的讨论里,大体上可以认为是已知的),因此这几个区域内存的分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题,当方法结束或者线程结束时,内存自然就随着回收了

Java堆和方法区这两个区域则有着很显著的不确定性:一个接口的多个实现类需要的内存可能会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有处于运行期间,我们才能知道这个程序究竟会创建那些对象。


如何判断对象是否该被回收?


引用计数法

在对象里面添加一个引用计数器,每当有一个对象引用它时,计数器值就加一;每当引用失效,计数器的值就减一;任何时刻计数器为零的对象就是不可能再被使用的

/**
 * @className: JVM_1
 * @description: 测试JVM是否使用引用计数法
 * @author: 热爱生活の李
 * @since: 2022/5/20 14:52
 */
public class JVM_1 {
    public Object instance = null;
    private static final int _1MB = 1024 * 1024;
    public static void main(String[] args) {
        JVM_1 objectA = new JVM_1();
        JVM_1 onjectB = new JVM_1();
        objectA.instance = onjectB;
        onjectB.instance = objectA;
        objectA = null;
        onjectB = null;
        System.gc();
    }
}

5de81675be124a8fa705ca3aba37eb7f.png

添加GC日志打印

运行配置里加VM option

参数为 -XX:+PrintGCDetails


缺点

可能会造成循环引用,导致无法回收,例如:上面对象objectA和objectB都有字段instance,objectA.instance = objectB,objectB.instance = objectA,除此之外,这两个对象再无其他任何引用,实际上这两个对象不可能被访问,但是他们互相引用对方,导致引用计数器不为0,就无法回收它们。


可达性分析算法

通过一系列称为“ GC Roots ” 的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“ 引用链 ” ,如果某个对象到GC Roots 间没有任何链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明对象不可能再被使用


那些可以作为GC Roots对象


固定作为GC Roots

1、在虚拟机栈中引用的对象(各个线程被调用的方法栈中的参数、局部变量、临时变量)

2、在方法区中类静态属性引用的对象(Java类的引用类型静态变量)

3、在方法区常量引用的对象(字符串常量池String Table的引用)

4、在本地方法栈中引用的对象(Native方法)

5、Java虚拟机内部的引用(基本数据类型对应的Class对象,一些常驻的异常对象比如NullPointException,还有系统类加载器)

6、所有被同步锁(synchronized关键字)持有的对象

7、反映Java虚拟机内部情况的JMXBean、JVMTL注册的回调、本地代码缓存等


根据用户所选择的垃圾收集器以及当前回收的内存区域不同,还可以用其他对象"临时性"地加入

引用

Java对引用的概念进行了扩充,将引用分为强引用(Strongly Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)


强引用: 指在程序代码之间存在的引用赋值,即类型Object obj = new Object() 这种引用关系。无论任何关系下,只要强引用关系还存在,垃圾收集器就不会回收掉被引用的对象

软引用: 用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如过这次回收还是没有足够的内存,才会抛出内存溢出异常。

弱引用: 用来描述那些非必要对象,但是它的强度比软引用更弱一些,关联的对象只能生存到下一次垃圾收集为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象

虚引用: 最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实列。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收的时候收到一个系统通知。

生存还是死亡

即使是在可达性分析算法中判断为不可达的对象也不是非死不可,要真正宣告一个对象死亡,至少要经历两次标记过程。

11707d60058640d5920251fae2731803.png

package JVM;
/**
 * @className: JVM_2
 * @description: 测试finalize()方法
 * @author: 热爱生活の李
 * @since: 2022/5/20 15:08
 */
public class JVM_2 {
    public static JVM_2 instance = null;
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize 方法执行了 !!!");
        JVM_2.instance = this;
    }
    public static void main(String[] args) throws InterruptedException {
        instance = new JVM_2();
        instance = null;
        System.gc();
        // finalizer方法优先级很低
        Thread.sleep(5000);
        if(instance != null){
            System.out.println("我还活着 !!!");
        }else {
            System.out.println("我死了 !!!");
        }
        instance = null;
        System.gc();
        Thread.sleep(5000);
        if(instance != null){
            System.out.println("我还活着 !!!");
        }else {
            System.out.println("我死了 !!!");
        }
    }
}

0a35048a218c49db9ee7ce18db2be970.pngfinalize()方法只会被调用一次


回收方法区

方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型


回收废弃的常量

假如一个字符串“Java” 曾经进入常量池,但是当前系统又没有任何一个字符串对象的值是“Java”,换句话说已经没有任何字符串对象引用常量池中的”Java“常量,且虚拟机中也没有其他地方引用这个字面量。如果这时发生内存回收,而且垃圾收集器判断有必要的话,这个”Java“常量就会被清理出常量池。常量池中其他类(接口)、方法、字段的符号引用也与此类似


回收类型的判断条件

1、该类的所有实例化都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例

2、加载该类的类加载器已经被回收

3、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法


Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是被允许,而并不是和对象一样,没有引用就被必然被回收。

关于是否要对类型进行回收,HotSpot虚拟机提供了 -Xnoclassgc 参数进行控制,还可以使用 -verbose:class 以及 -XX:+TraceClassLoading、-XX:TraceClassUnLoading 查看类加载和卸载信息


相关文章
|
1月前
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【10月更文挑战第4天】在大数据时代,算法效率至关重要。本文从理论入手,介绍时间复杂度和空间复杂度两个核心概念,并通过冒泡排序和快速排序的Python实现详细分析其复杂度。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1);快速排序平均时间复杂度为O(n log n),空间复杂度为O(log n)。文章还介绍了算法选择、分而治之及空间换时间等优化策略,帮助你在大数据挑战中游刃有余。
54 4
|
27天前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
53 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
15天前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
28天前
|
算法 Java
JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
本文详细介绍了JVM中的GC算法,包括年轻代的复制算法和老年代的标记-整理算法。复制算法适用于年轻代,因其高效且能避免内存碎片;标记-整理算法则用于老年代,虽然效率较低,但能有效解决内存碎片问题。文章还解释了这两种算法的具体过程及其优缺点,并简要提及了其他GC算法。
 JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
|
1月前
|
存储 算法 Java
【JVM】垃圾释放方式:标记-清除、复制算法、标记-整理、分代回收
【JVM】垃圾释放方式:标记-清除、复制算法、标记-整理、分代回收
46 2
|
22天前
|
算法
PID算法原理分析
【10月更文挑战第12天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
28天前
|
算法
PID算法原理分析及优化
【10月更文挑战第6天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
1月前
|
算法 安全 Go
Python与Go语言中的哈希算法实现及对比分析
Python与Go语言中的哈希算法实现及对比分析
35 0
|
18天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
3天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。
下一篇
无影云桌面