【Java】垃圾回收学习笔记(一):Root Search 根可达算法+垃圾回收的起点

文章目录

  • 1. 引用计数法
      • 优点
      • 缺点
  • 2. 可达性分析 Root Search
    • 2.1 那些对象是GC Roots
    • 2.2 引用的分类
    • 2.3 回收方法区
  • 3. 实现细节
    • 3.1 GC的起点:节点枚举
      • OopMap:帮助高效的根节点枚举
    • 3.2 何时开始GC:安全点与安全区域
      • 如何选取安全点
      • 如何让程序进入安全点
    • 3.3 程序不执行时的安全点:安全区域
  • Reference

最近上班地铁上偶尔看看书,周末有空理一下,做个笔记。
下面说说GC过程中如何判断对象是否存活。

1. 引用计数法

用于微软COM(Component Object Model)计数、Python语言等,进行内存管理。原理就是在对象中添加一个引用计数器:

  • 每当有地方引用他时,计数器加一;
  • 引用失效时,计数器减一;
  • 计数器为零时,对象就是不可能再被使用的。

优点

原理简单,判断效率高

缺点

单纯的引用计数很难解决对象间的相互循环引用问题,需要考虑很多额外情况

另外,Java虚拟机并不用引用计数来判断对象是否存活

2. 可达性分析 Root Search

Java、C#等主流商用程序语言凑采用可达性分析(Reachability Analysis)算法判定对象是否存活:

  1. 枚举一系列对象作为GC Roots
  2. 从GC Roots出发,搜索引用链,某个对象不可达时,则该对象判定为不可能再被使用(死亡)

在这里插入图片描述

2.1 那些对象是GC Roots

Java中,可作为GC Roots的对象包括:

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,也就各个线程调用的对象;
  • 方法区中,类静态属性引用的对象;
  • 方法区中,常量引用的对象,比如字符串常量池中的引用;
  • 本地方法栈(Native方法)引用的对象;
  • JVM内部的引用,比如
    • 基本数据类型(float、double、int、long、byte、char、boolean)对应的Class对象
    • 常驻的异常对象(NullPointException、OutOfMemoryError)
    • 类加载器(BootstrapClassLoader、ExtensionClassLoader、AppClassLoaderfab)等等;
  • synchronized关键字持有的锁对象(一般是static对象,或者是当前类的Class对象);
  • 反应JVM内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

在分代回收和局部回收过程中,可能会有临时对象加入GC Roots集合,来解决跨代引用等问题。

2.2 引用的分类

JDK1.2之后,Java扩充了引用的定义,不同引用在GC时的行为有所差异:

  • 强引用(Strongly Reference):在代码中通过Object o = new Object() 形势产生的引用关系。只要强引用存在,GC就不会回收被引用对象(哪怕OOM)。

  • 软引用(Soft Reference):通过SoftReference 类实现的引用。系统内存发生内存溢出前,会将软引用对象列入回收范围进行回收,如果回收后还没有足够内存,就抛出OOM。

  • 弱引用(Weak Reference):通过WeakReference 类实现的引用。无论内存是否足够,弱引用对象都会被GC。

  • 虚引用(Phantom Reference):通过PhantomReference 类实现的引用。虚引用无法对对象实际的生存时间造成影响,也不能通过虚引用来获得对象实例。虚引用的作用就是追踪对象的GC信息,在被回收时加入到关联的引用队列,可以得到一个通知或做些其他事。

    import java.lang.ref.PhantomReference;
    import java.lang.ref.ReferenceQueue;
    public class PhantomReferenceExample {
        public static void main(String[] args) {
            Object obj = new Object();
            ReferenceQueue<Object> queue = new ReferenceQueue<>();
            PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);
     
            // 执行对象的清理操作
            Thread cleanupThread = new Thread(() -> {
                try {
                    while (true) {
                        PhantomReference<?> reference = (PhantomReference<?>) queue.remove();
                        // 执行清理操作,比如释放资源
                        System.out.println("Cleanup: " + reference);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            cleanupThread.setDaemon(true);
            cleanupThread.start();
     
            obj = null; // 取消强引用
            System.gc(); // 手动触发垃圾回收
     
            // 在这里可以进行一些其他操作
        }
    }
    

    根可达算法中判定为不可达的对象,可以在finallize()方法中重新增加引用关联来复活(只能复活一次,try…finally也行)

2.3 回收方法区

《Java虚拟机规范》:可以不要求虚拟机在方法区中实现垃圾回收(比如JDK11 的ZGC收集器就不支持类卸载)。因为方法区回收的性价比通常较低。

方法区允许(不是必须)回收的对象一般是废弃的常量不再使用的类

  • 废弃的常量:类似堆中的对象,即没引用的常量。比如常量池中有个字符串对象,值为“java”,其回收条件为
    • 没用其他任何对象引用“java”常量;
    • 虚拟机中没有“java”字面量的引用。
  • 不再被使用的类需要同时满足三个条件才会允许(不是必须)被回收(即类卸载):
    • 该类的所有实例都已经被回收(堆中不存在该类及其子类的实例);
    • 加载该类的类加载器已经被回收;
    • 该类对应的Class对象也没被引用(即不能通过反射访问该类的method跟filed)。

Hotspot虚拟机提供了-Xnoclassgc参数进行类卸载控制,还有一系列参数查看类可查看类加载和类卸载信息。

在动态代理大量使用的场景(大量使用反射、CGLib等),或者需频繁自定义类加载器等场景中,通常需要JVM具备类卸载能力,以保证生成的代理类对象不会对方法区造成过大压力。

3. 实现细节

简单来说就是,在安全点进行GC roots枚举,然后开始可达性分析(比如三色标记),标记完后由垃圾收集器采用特定的回收算法进行内存回收。

3.1 GC的起点:节点枚举

由于用户进程运行过程中,根节点集合的对象引用关系会不断变化,为了保证一致性,在根节点枚举过程中需要停顿所有用户线程,不可避免地面临“Stop The World”(但由GC Roots查找引用链的过程已经可以与用户线程并发了)

OopMap:帮助高效的根节点枚举

实际上,停止用户线程进行根节点枚举的时候,不需要一个不漏地检查所有执行的上下文和全局引用。HotSpot使用了OopMap()来直接得到引用信息,在类加载完成后,HotSpot会计算对象内某个偏移量上时什么类型的数据,也会在即时编译的过程中记录栈帧和寄存器里的引用位置。

3.2 何时开始GC:安全点与安全区域

OopMap可以帮助JVM快速完成根节点枚举。但是程序不同线程是按一条条指令执行的,许多指令都可能导致对象引用关系的变化,如果为每条指令都生产OopMap会消耗大量的内存空间,因此可以在“特定位置”生产OopMap。前面有提到,根节点枚举会有STW来避免对象引用关系的变化,现在的问题就是:

在何时让这些线程停止?(即在什么时候生成OopMap/开始GC)

在HotSpot中,这个“特殊位置”就是安全点(Safe Point),用户程序执行时,强制要求执行到安全点后才能暂停下来,开始GC。

再明确一下,安全点就是GC的起点,程序需要GC时,所有线程需要跑到对应的安全点,安全点处有对应的OopMap,可以帮助虚拟机进高效地搜索GC root集合,然后开始GC流程。

如何选取安全点

安全点选的太少,会让收集器等待时间太久;安全点选的太频繁,会增大运行时的内存负荷(每个安全点都会有对应的OopMap)。

安全点的选取标准:**能否让程序长时间执行。**即指令序列的复用,比如方法调用、循环跳转、异常跳转等就会产生安全点。

为什么安全点需要让程序一直跑/为什么安全点要考虑指令序列的复用?

可以理解为,在安全点的程序指令运行时间较长(复用较多),需存活的对象相对重复,不能让过多的死对象占用内存,因此适合作为GC的起点。

如何让程序进入安全点

即如何让GC发生时,让所有线程都跑到最近的安全点。一般包括两种方案:

  • 抢先式中断:GC发生时,中断所有线程,如果有线程没到安全点,就恢复执行(现在几乎没有虚拟机用抢先式中断);
  • 主动式中断:需要中断线程时,垃圾收集器设置一个标志位,各个线程不断主动轮询该标志位,一旦标志位位true,线程在自己最近的安全点刮起。
    • 轮询的标志位与安全点重合,且需要包括创建对象等需要在堆上分配内存的地方,避免没有足够内存分配新对象(是否有必要GC)。

3.3 程序不执行时的安全点:安全区域

如果程序当前没分配执行时间(Sleep或Block状态),就走不到安全点,然后挂起,等待开始GC。因此可以把这些没分配执行时间的位置作为安全区域(Safe Region)。与安全点类似,在安全区域中,引用关系不会发生变化。

  • 当用户线程执行到安全区域时,会标识自己进入安全区域,当虚拟机需要GC时就不用管这些标识进入安全区域的线程。
  • 当线程要离开安全区域时,需检查根节点枚举是否完成根节点枚举(或其他STW阶段),如果完成则继续执行,如果未完成则一直等待知道收到完成的信号。

Reference

《深入理解java虚拟机:JVM高级特性与最佳时间(第3版)》 周志明
ps:虚引用的例子是也是网上找的,但原文找不到了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/782640.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在mac下 Vue2和Vue3并存 全局Vue2环境创建Vue3新项目(Vue cli2和Vue cli4)

全局安装vue2 npm install vue-cli -g自行在任意位置创建一个文件夹vue3&#xff0c;局部安装vue3,注意不要带-g npm install vue/cli安装完成后&#xff0c;进入目录&#xff0c;修改vue为vue3 找到vue3/node-moudles/.bin/vue&#xff0c;把vue改成vue3。 对环境变量进行配置…

web安全基础名词概念

本节内容根据小迪安全讲解制作 第一天 域名&#xff1a; 1.1什么是域名&#xff1f; 网域名称(英语&#xff1a;Domain Name&#xff0c;简称&#xff1a;Domain)&#xff0c;简称域名、网域&#xff0c;是由一串用点分隔的字符组成的互联网上某一台计算机或计算机组的名称&a…

java核心-泛型

目录 概述什么是泛型分类泛型类泛型接口泛型方法 泛型通配符分类 泛型类型擦除分类无限制类型擦除有限制类型擦除 问题需求第一种第二种 概述 了解泛型有利于学习 jdk 、中间件的源码&#xff0c;提升代码抽象能力&#xff0c;封装通用性更强的组件。 什么是泛型 在定义类、接…

存储过程编程-创建(CREATE PROCEDURE)、执行(EXEC)、删除(DROP PROCEDURE)

一、定义 1、存储过程是在SQL服务器上存储的已经编译过的SQL语句组。 2、存储过程分为三类&#xff1a;系统提供的存储过程、用户定义的存储过程和扩展存储过程 &#xff08;1&#xff09;系统提供的存储过程&#xff1a;在安装SQL Server时&#xff0c;系统创建了很多系统存…

Kafka(一)基础介绍

一&#xff0c;Kafka集群 一个典型的 Kafka 体系架构包括若Producer、Broker、Consumer&#xff0c;以及一个ZooKeeper集群&#xff0c;如图所示。 ZooKeeper&#xff1a;Kafka负责集群元数据的管理、控制器的选举等操作的&#xff1b; Producer&#xff1a;将消息发送到Broker…

MySQL事务隔离

MySQL事务隔离 前言锁共享锁&#xff08;Shared Lock&#xff09;排他锁&#xff08;Exclusive Lock&#xff09;行级锁&#xff08;Row-Level Lock&#xff09;表级锁&#xff08;Table-Level Lock&#xff09;快照读和当前读查看锁 事务事务的四个特性事务的并发问题事务的隔…

Chrome 127内置AI大模型攻略

Chrome 127 集成Gemini:本地AI功能 Google将Gemini大模型整合进Chrome浏览器,带来全新免费的本地AI体验: 完全免费、无限制使用支持离线运行,摆脱网络依赖功能涵盖图像识别、自然语言处理、智能推荐等中国大陆需要借助魔法,懂都懂。 安装部署步骤: 1. Chrome V127 dev …

golang验证Etherscan上的智能合约

文章目录 golang验证Etherscan上的智能合约为什么要验证智能合约如何使用golang去验证合约获取EtherscanAPI密钥Verify Source Code接口Check Source Code Verification Status接口演示示例及注意事项网络问题无法调用Etherscan接口&#xff08;最重要的步骤&#xff09; golan…

YoloV9改进策略:Block改进|轻量实时的重参数结构|最新改进|即插即用(全网首发)

摘要 本文使用重参数的Block替换YoloV9中的RepNBottleneck&#xff0c;GFLOPs从239降到了227&#xff1b;同时&#xff0c;map50从0.989涨到了0.99&#xff08;重参数后的结果&#xff09;。 改进方法简单&#xff0c;只做简单的替换就行&#xff0c;即插即用&#xff0c;非常…

保健品商城小程序模板源码

保健品商城小程序模板源码 简洁通用的保健品&#xff0c;健康生活&#xff0c;零售商品&#xff0c;电子商务微信小程序前端模板下载。包含&#xff1a;主页、购物车、客服、个人中心、我的订单、商品详情、我的钱包、设置等等。 保健品商城小程序模板源码

程序员如何做好需求判断?

1. 导语 本文作为2024上半年核心思考之二。 通过他人经验传导、个人实践、广泛阅读书籍(方法论类、企业经营类、传记类、财务类&#xff0c;具体书单附文末)&#xff0c;学会基于更高阶的经营者视角来做好业务需求判断。本文思路如下&#xff1a; 首先&#xff0c;抛一个灵魂问…

【server】springboot 整合 redis

1、redis 使用模式 1.1 单机模式 1.1.1 编译安装方式 1.1.1.1 下载 Redis的安装非常简单&#xff0c;到Redis的官网&#xff08;Downloads - Redis&#xff09;&#xff0c;下载对应的版本&#xff0c;简单几个命令安装即可。 1.1.1.2 编译安装 tar xzf redis-stable.tar.…

IDEA 开发工具

IDEA 开发工具 IDEA软件激活新建项目新建project 运行调试 IDEA软件激活 访问激活码网进入带*的域名下载并解压左上角的zip包先执行sh uninstall.sh&#xff0c;再执行sh install.sh在带*的网页中复制并使用激活码code 新建项目 新建project file》New〉Project》New Proje…

【测试】系统压力测试报告模板(Word原件)

系统压力测试&#xff0c;简而言之&#xff0c;是在模拟高负载、高并发的环境下&#xff0c;对系统进行全面测试的过程。它旨在评估系统在面对极端使用条件时的性能表现&#xff0c;包括处理能力、响应时间、资源消耗及稳定性等关键指标。通过压力测试&#xff0c;开发团队能够…

MySQL之备份与恢复和MySQL用户工具(一)

备份与恢复 备份脚本化 为备份写一些脚本是标准做法。展示一个示例程序&#xff0c;其中必定有很多辅助内容&#xff0c;这只会增加篇幅&#xff0c;在这里我们更愿意列举一些典型的备份脚本功能&#xff0c;展示一些Perl脚本的代码片段。你可以把这些当作可重用的代码块&…

Python酷库之旅-第三方库Pandas(009)

目录 一、用法精讲 19、pandas.read_xml函数 19-1、语法 19-2、参数 19-3、功能 19-4、返回值 19-5、说明 19-6、用法 19-6-1、数据准备 19-6-2、代码示例 19-6-3、结果输出 20、pandas.DataFrame.to_xml函数 20-1、语法 20-2、参数 20-3、功能 20-4、返回值 …

【国产开源可视化引擎Meta2d.js】网格

画布背景网格 在线体验&#xff1a; 乐吾乐2D可视化 示例&#xff1a; // 设置默认缺省网格属性 meta2d.store.options.grid true; // 开启 meta2d.store.options.gridColor eeeeee; // 网格线条颜色 meta2d.store.options.gridSize 10; // 格子大小// 设置单个图纸的网格…

Golang | Leetcode Golang题解之第222题完全二叉树的节点个数

题目&#xff1a; 题解&#xff1a; func countNodes(root *TreeNode) int {if root nil {return 0}level : 0for node : root; node.Left ! nil; node node.Left {level}return sort.Search(1<<(level1), func(k int) bool {if k < 1<<level {return false}…

【ETABS】【RHINO】案例:Swallow to ETABS

文章目录 01. Swallow Overview总览1 LOAD&#xff1a;Defination of LoadCase、Response Combo2 SectionArea Section and Area Load&#xff08;面截面定义与指定&#xff0c;面荷载指定&#xff09;Frame Section with rebarattr and linear load&#xff08;带钢筋属性框架…

flutter:监听路由的变化

问题 当从路由B页面返回路由A页面后&#xff0c;A页面需要进行数据刷新。因此需要监听路由变化 解决 使用RouteObserver进行录音监听 创建全局变量&#xff0c;不在任何类中 final RouteObserver<PageRoute> routeObserver RouteObserver<PageRoute>();在mai…