首页 > 编程语言 >JVM(十一)垃圾回收概述和垃圾标记阶段的算法

JVM(十一)垃圾回收概述和垃圾标记阶段的算法

时间:2023-07-12 10:45:08浏览次数:42  
标签:finalize 对象 内存 回收 算法 垃圾 JVM 引用

JVM(十一)垃圾回收概述和垃圾标记阶段的算法


1 Java垃圾回收概述

  • 什么是垃圾?

    • 垃圾是在程序运行过程中不被任何指针指向的对象,这个对象就是需要被回收的垃圾
  • 为什么要进行垃圾回收?

    • 如果不及时对内存中的垃圾进行清理,那么这些垃圾对象所占内存空间会一直保存到应用程序结束,被保留的空间无法被其他对象所使用,甚至会导致内存溢出
    • 垃圾回收还可以清除内存里的记录碎片,将堆内存移到堆的另一边,以便将整理的内存分配给新的对象
  • 自动内存管理,无需开发人员手动参与内存的分配与回收,降低内存泄露和内存溢出的风险

  • 将程序员从繁重的内存管理中释放出来,更专心地注重于业务开发

  • 当需要排查各种内存溢出、内存泄露问题的时候,当垃圾回收成为系统达到更高并发量的瓶颈的时候,就必须对这些自动化的技术实施必要的监控和调节

  • 只有JVM内存区域的方法区和堆存在垃圾回收,Java栈、本地方法栈存在栈溢出的情况而没有垃圾回收,程序计数器则既没有垃圾回收也没有溢出的情况

image-20230704152736441

2 垃圾回收的相关算法

3 垃圾标记阶段的算法

  • 在堆中几乎存放着所有的java对象实例,在执行垃圾回收之前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象,只有被标记为已经死亡的对象,GC才会在执行垃圾回收的时候释放掉其所占有的空间,因此这个过程也被称作垃圾标记阶段
  • 当一个对象不被任何存活的对象继续引用的时候,就可以宣判这个对象死亡了
  • 判断对象存活一般有两种方式:引用计数算法可达性分析算法
3.1 引用计数算法
  • 引用计数算法即是对每个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况

  • 每当有其他的对象对这个对象进行了引用,则对象的引用计数器就加一,引用失效的时候,就将引用计数器减一;引用计数器减到0的时候就表示对象不可再被使用,从而进行回收

  • 优点

    • 实现简单,垃圾对象易于辨识,判定效率高且回收没有延迟性
  • 缺点

    • 需要单独的引用计数器,增加了存储空间的开销
    • 每次赋值都需要更新计数器,伴随着加法和减法操作,增加了时间开销
    • 引用计数器无法处理循环引用的问题,这一缺陷导致了Java的垃圾回收器中没有使用这一算法

    引用计数器无法处理循环引用问题,是因为循环引用会导致对象的引用计数始终不为0,无用的对象得不到回收从而导致内存泄露问题

    image-20230704162116157
3.2 可达性分析算法

可达性分析算法又称为根搜索算法追踪性垃圾收集,可达性分析算法不仅可以具备实现简单和执行高效的特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄露的发生GC Roots意思是一群活跃的引用的根集合,可达性分析算法的基本实现思路为:

  • 可达性分析算法以根集合(GC Roots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达
  • 使用可达性分析算法后,内存中存活的对象会被根对象集合直接或者间接连接着,搜索所走过的路径称作引用链(Reference Chain)
  • 可达性分析算法中,只有能被根对象集合直接或者间接连接的对象才是存活对象
  • 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象已经死亡,可以被标记为垃圾对象
image-20230704164945222

在Java中,GC Roots包括以下几类元素:

  • 虚拟机栈中引用的对象,比如:各个线程被调用的方法的参数、局部变量等

  • 本地方法栈内本地方法引用的对象

  • 方法区中静态属性引用的对象,如Java类的引用类型静态变量

  • 方法区中常量引用的对象,如字符串常量池中的引用

  • 被同步锁Synchronized持有的对象(同步监视器)

  • Java虚拟机内部的引用,包括基本数据类型对应的Class对象以及一些常驻的异常对象(如:NullPointerException、OutOfMemoryError)以及系统类加载器

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

  • 除了上面这些固定的GC Roots集合外,根据用户所选用的垃圾收集器以及当前回收的内存区域的不同,如分代收集局部回收的时候,还可以有其他相关联的对象“临时性”地加入

    这是因为如果只针对Java堆中的一块区域进行垃圾回收(如YGC只针对新生代),必须考虑到内存区域是虚拟机自己的实现细节,而不是孤立封闭的,所以这个区域的对象有可能被其他区域的对象所引用,这时候就需要一并将关联的区域对象也加入GC Roots集合中去考虑,才能保证可达性分析的准确性

  • 如果使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能够保障一致性的快照中进行,这点不满足的话就无法保证分析结果的准确性

    这点也是导致GC进行时必须Stop The World的一个重要原因,即使是号称几乎不会停顿的CMS收集器,在枚举根节点的时候也是必须要停顿的

”由于Root采用的是栈方式存放变量和指针,所以如果一个指针,它保存了堆内存的对象而自己又不存放于堆内存中,它就是一个Root“

这句话JDK6之后就有问题了,因为JDK6开始静态变量和常量池都是放在堆里面

image-20230704192033350

4 对象的finalization机制

  • finalization机制用于对象销毁之前的自定义处理逻辑当垃圾回收器发现没有引用指向一个对象的时候,即在回收这个方法之前,总会调用这个对象的finalize()方法

  • finalize()方法允许在子类中被重写,常用于对象被回收时进行资源的释放:通常在这个方法中进行一些资源的释放和清理工作,如关闭文件、套接字和数据库连接等

  • 永远不要主动调用某个对象的finalize()方法,应该交给垃圾回收机制调用,因为:

    • finalize()方法可能会导致对象的复活
    • finalize()方法的执行时间没有保障,完全由GC线程决定,极端情况下不发生GC则永远不会调用该方法
    • 一个糟糕的finalize()方法会严重影响GC的性能
  • 由于finalize()方法的存在,一个不可达、无法触及的对象有可能在某一条件下复活自己,因此虚拟机中的对象一般处于三种可能的状态:

    • 可触及的:从根节点开始,可以到达这个对象
    • 可复活的:对象的所有引用都被释放,但是对象有可能在finalize()中被复活
    • 不可触及的:对象的finalize()方法被调用但是没有被复活,因此就会进入不可触及状态

    不可触及的对象不能被复活,因为finalize()方法只能被调用一次

    只有不可触及的对象才可以被回收

  • 详细来说,判断一个对象是否可以回收,至少要经过两次标记过程

    1. 如果对象到根集合GC Roots没有引用链,则进行第一次标记
    2. 进行筛选,判断对象是否有必要执行finalize()方法
      1. 如果对象没有重写finalize()方法,或者finalize()方法已经被虚拟机调用过,则虚拟机视为“没有必要执行”,对象直接被判定为不可触及的
      2. 如果对象重写了finalize()方法,而且还未被执行过,则对象会被插入到F-Queue队列中,然后先进先出,由一个虚拟机自动创建的、低优先级的Finalizer线程触发其finalize()方法进行执行
      3. finalize()方法是对象逃脱死亡的最后机会,稍后GC会对F-Queue队列中的对象进行第二次标记;如果该对象在此过程中与引用链上的任何一个对象建立了联系,则对象就被移除队列,之后再出现没有引用指向的情况,finalize()方法就不会再次被调用,对象直接会变成不可触及状态——也就是说:finalize()方法只会被调用一次
4.1 对象复活演示实例
public class CanReliveObj {
    public static CanReliveObj obj;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("调用当前方法的finalize()方法");
        // 当前待回收的对象指向了引用链上的对象
        obj = this;
    }

    public static void main(String[] args) throws InterruptedException {
        obj = new CanReliveObj();
        obj = null;
        System.gc();
        System.out.println("第一次gc");
        // finalizer线程优先级远低于主线程,防止其得不到执行
        // 执行finalize方法,对象复活成功
        Thread.sleep(2000);
        if(obj == null) {
            System.out.println("obj is dead.");
        } else {
            System.out.println("obj is still alive.");
        }
        obj = null;
        System.gc();
        // finalize方法只能被执行一次,因此第二次对象自救失败
        if(obj == null) {
            System.out.println("obj is dead.");
        } else {
            System.out.println("obj is still alive.");
        }
    }
}

5 MAT和JProfiler的GC Roots溯源

标签:finalize,对象,内存,回收,算法,垃圾,JVM,引用
From: https://www.cnblogs.com/tod4/p/17546905.html

相关文章

  • 初识虚拟机JVM
    初识JVM(JAVAVirtualMachine)​ JVM是一种规范,可以使用软件来实现,也可以使用硬件来实现,就是一个虚拟的用于执行bytecodes字节码的计算机。他也定义了指令集、寄存器集、结构栈、垃圾收集堆、内存区域。​ JVM负责将java字节码解释运行,边解释边运行,这样,速度就会受到一定的影......
  • Day05_垃圾回收机制
    1.Day04的温故知新: 2.今日内容: 3.列表在内存当中存值的方式: 4.1.直接引用和间接引用: 4.2.直接引用和间接引用: 5.1.标记清除_循环引用: ......
  • 算法学习day14二叉树part01-94、144、145
    packageSecondBrush.Tree;importjava.util.ArrayList;importjava.util.List;/***94.二叉树的中序遍历*给定一个二叉树的根节点root,返回它的中序遍历。**/publicclassBinaryTreeInorderTraversal_94{publicList<Integer>inorderTraversal(Tre......
  • 数据结构与算法 #18 下跳棋,极富想象力的同向双指针模拟
    ⭐️本文已收录到AndroidFamily,技术和职场问题,请关注公众号[彭旭锐]和[BaguTreePro]知识星球提问。学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭将基于Java/Kotlin语言,为你分享常......
  • 浅谈BIT本科数据结构与算法课程 1
    关于C++基本输入输出流#include<bits/stdc++.h>usingnamespacestd;intmain(){ inta,b; cin>>a>>b; cout<<a<<endl; return0;}栈和队列关于stl#include<algorithm>vector<int>x;x.push_back(n);x.pop_back();x.back();x[1......
  • JVM(六)堆
    JVM(六)堆1核心概述几乎所有的对象实例和数组都是分配在堆上的(栈不会存储数组和对象,栈帧中的局部变量表只会存储指向堆中实例的引用)一个Java进程对应一个JVM实例,一个JVM实例只存在一个堆内存,堆也是内存管理的核心区域堆和方法区是线程共享的,但堆也有划分的线程私有缓冲区......
  • 负载均衡算法的选择
    负载均衡算法的选择应该根据具体的应用场景和需求来确定。以下是一些常见的负载均衡算法及其适用场景:轮询(RoundRobin):适用于请求处理时间相对均匀的场景,能够实现简单的请求分配。加权轮询(WeightedRoundRobin):适用于不同后端服务器性能不同的场景,可以根据服务器的性能设置不同......
  • C++进制转换+扫描线算法(二维区间合并面积和)
    ......
  • 代码随想录算法训练营第二十九天| 1005.K次取反后最大化的数组和 134. 加油站 135. 分
      860.柠檬水找零 思路:遇到20,先给10和5,再给三个5代码:1boollemonadeChange(vector<int>&bills){2if(bills.size()==0)returntrue;34map<int,int>currentMoney;5for(inti=0;i<bills.size();i++)6{7if......
  • MATLAB代码:计及电转气协同的含碳捕集与垃圾焚烧虚拟电厂优化调度
    MATLAB代码:计及电转气协同的含碳捕集与垃圾焚烧虚拟电厂优化调度关键词:碳捕集虚拟电厂需求响应优化调度电转气协同调度参考文档:《计及电转气协同的含碳捕集与垃圾焚烧虚拟电厂优化调度》完全复现仿真平台:MATLAB+CPLEX主要内容:代码主要做的是一个计及电转气协同的含碳捕集与......