首页/新闻资讯/正文详情

理解 GC Roots:对象为什么不能被回收

发布时间:2026/11/3 2:19:23 来源:云帆数科 栏目:资讯中心
理解 GC Roots:对象为什么不能被回收
Java 程序运行时会不断创建对象但内存是有限的。JVM 必须定期清理不再使用的对象否则内存迟早会耗尽。问题在于JVM 并不理解业务。它不知道某个订单以后还会不会查询也不知道某个用户对象是否已经失去价值。它只能判断一件事程序现在还能不能找到这个对象GC Roots 和可达性分析都是围绕这个问题建立的。一、GC 判断对象存活的基本原理1. 为什么不能根据“有没有引用”判断假设两个对象相互引用PersonanewPerson();PersonbnewPerson();a.friendb;b.frienda;堆中的关系是Person A ↔ Person B随后程序不再持有它们anull;bnull;虽然 A 和 B 仍然互相引用但程序已经无法访问它们。它们应当被回收。这说明对象被其他对象引用不代表程序仍然能够使用它。因此GC 不能只统计一个对象有没有引用而要判断它是否仍然处在程序的可访问范围内。2. GC Roots 是搜索的起点为了判断程序能否访问一个对象GC 必须从一组确定的位置开始搜索。这些搜索起点叫作GC Roots。可以把堆内存理解成一张由对象和引用组成的图入口 A → 对象 1 → 对象 2 入口 B → 对象 3 → 对象 4 对象 5 ↔ 对象 6GC 从入口 A、B 开始沿引用关系向下搜索。它能找到对象 1、2、3、4因此这些对象是可达的。对象 5、6 虽然互相引用但从入口无法找到所以它们不可达。从 GC Roots 能找到 → 对象可达 → 不能回收 从所有 GC Roots 都找不到 → 对象不可达 → 可以回收因此GC Roots 本质上不是某个特殊 Java 对象而是 JVM 进行存活判断时使用的一组搜索入口。二、GC Roots 在代码中是什么理解 GC Roots不需要一开始背完整列表。日常业务开发中先掌握三个最常见的来源即可。1. 当前方法中的局部变量publicvoidcreateOrder(){OrderordernewOrder();process(order);}方法执行期间当前线程的栈帧保存着局部变量order当前线程的活动栈 └── 局部变量 order └── Order 对象GC 可以从当前线程栈中的引用找到Order所以对象可达。方法结束后栈帧消失。如果没有其他地方保存这个对象引用路径也就断开了GC Roots Order │ └────────× 无法找到此时Order才具备被回收的条件。需要注意方法结束并不代表对象立即消失只代表对象可能变成不可达等待之后的 GC 回收。2. 静态字段publicclassOrderCache{privatestaticfinalMapLong,OrderCACHEnewHashMap();}写入订单后CACHE.put(1001L,newOrder());会形成一条长期引用链类相关的 GC Root → static CACHE → HashMap → Order即使创建订单的方法已经结束订单仍然被静态 Map 持有。只要没有执行删除、清空或淘汰GC 就能沿这条路径找到订单因此不能回收。3. 正在运行的线程正在运行的线程也可能长期持有对象。例如线程池中的ThreadLocalprivatestaticfinalThreadLocalUserContextCONTEXTnewThreadLocal();请求执行时CONTEXT.set(newUserContext());可能形成活动线程 → ThreadLocalMap → UserContext线程池中的线程不会随一次请求结束而销毁。如果请求结束后没有调用remove()UserContext就可能继续被线程持有。因此通常要写成try{CONTEXT.set(newUserContext());handleRequest();}finally{CONTEXT.remove();}remove()并不是命令 GC 立即回收而是在业务结束后主动断开不再需要的引用路径。三、内存泄漏是怎样产生的1. 内存泄漏的本质Java 中的内存泄漏可以定义为对象在业务上已经没有价值但仍然能够从 GC Roots 找到因此 GC 无法回收。这里包含两个不同维度业务维度对象已经没有用了 JVM 维度对象仍然可以从 GC Roots 找到当两者同时成立时就产生了内存泄漏。这也解释了为什么 GC 无法自动解决所有内存问题GC 只能判断对象是否可达不能判断对象是否还有业务价值。2. 一个无界缓存案例假设订单服务使用静态 Map 作为缓存privatestaticfinalMapLong,OrderCACHEnewConcurrentHashMap();publicOrderquery(longorderId){Orderorderrepository.findById(orderId);CACHE.put(orderId,order);returnorder;}随着不同订单不断被查询CACHE ├── Order 1001 ├── Order 1002 ├── Order 1003 ├── ... └── Order 3000000很多旧订单可能再也不会访问但引用链始终存在GC Root → static CACHE → ConcurrentHashMap → 历史 Order对业务来说这些订单已经失去缓存价值对 GC 来说它们仍然可达。因此 GC 不能回收。随着对象不断积累系统会经历缓存条目持续增加 → 长期存活对象越来越多 → GC 后内存无法回落 → Full GC 越来越频繁 → 可用堆耗尽 → OOM问题不在于 GC 没有执行而在于应用一直通过强引用告诉 GC“这些对象仍然需要保留。”3. 内存占用大不一定是泄漏再看一个报表场景publicvoidgenerateReport(){ListOrderordersrepository.queryAll();reportGenerator.generate(orders);}生成报表期间大量订单对象确实需要留在内存中当前方法 → List → 大量 Order方法结束后如果没有其他地方保存这些对象引用路径就会断开。它们随后可以被 GC 回收。因此判断内存泄漏不能只看对象数量而要看两个问题业务结束后对象是否仍然存活对象是否仍然存在到 GC Roots 的引用路径可以据此区分表现本质暂时占用很高业务结束后能够回落大内存工作集对象仍有业务价值但可用内存不足容量规划问题对象已无业务价值却长期保持可达内存泄漏数据不断进入长期容器且没有上限容量失控四、如何排查一个对象为什么没有被回收排查内存问题时不应该只问“什么对象最多”而要沿着引用关系继续追问。1. 第一步找到异常对象Heap Dump 的 Histogram 可以告诉我们哪些对象数量多、占用大。例如OrderDetail HashMap$Node String byte[]但 Histogram 只能说明现象。byte[]很多不代表字节数组本身存在问题。它们可能只是被String或OrderDetail间接持有。2. 第二步找到主要持有者Dominator Tree 用于观察谁间接保留了大量内存。例如OrderCacheService.LOCAL_CACHE └── ConcurrentHashMap └── 大量 OrderDetail这时可以初步判断大量订单对象可能由同一个缓存容器持有。3. 第三步找到 GC Roots 路径最后查看Path to GC RootsSystem Class → OrderCacheService.class → static LOCAL_CACHE → ConcurrentHashMap → OrderDetail这条路径回答的是为什么OrderDetail经历 GC 后仍然活着因为它仍然可以从 GC Roots 沿引用链找到。但到这里还不能直接宣布“发生了内存泄漏”。还需要回到业务代码确认这些订单是否仍然有缓存价值缓存是否存在最大容量是否存在过期和淘汰订单 ID 是否持续增长谁应该在什么时候删除这些对象。工具能证明对象为什么活着业务分析才能判断对象是否应该继续活着。结语GC Roots、可达性分析和内存泄漏本质上围绕同一条逻辑GC 从 Roots 出发 → 沿引用关系搜索对象 → 能找到的对象保留 → 找不到的对象可以回收内存泄漏发生在对象已经没有业务价值 → 但引用路径没有断开 → 对象仍然可达 → GC 无法回收因此排查内存问题时最有价值的不是背诵 GC Roots 的类型而是连续追问这个对象是谁持有的 持有者为什么还活着 引用路径为什么没有断开 对象在业务上是否仍然有价值 谁应该负责结束它的生命周期最终记住一句话即可GC Roots 决定 GC 从哪里开始寻找对象可达性决定对象能不能被回收内存泄漏则意味着对象虽然已经没用却仍然存在一条通往 GC Roots 的引用路径。

相关推荐

【数据治理(2)】数据工程生命周期:从数据产生到价值实现的判断框架
【数据治理(2)】数据工程生命周期:从数据产生到价值实现的判断框架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 2:19:23

3 分钟看懂 Debezium:数据库变更捕获怎么做到毫秒级、不动业务库
3 分钟看懂 Debezium:数据库变更捕获怎么做到毫秒级、不动业务库

3 分钟看懂 Debezium:数据库变更捕获怎么做到毫秒级、不动业务库 【免费下载链接】debezium Change data capture for a variety of databases. Please log issues at https://github.com/debezium/dbz/issues. 项目地址: https://gitcode.com/gh_mirrors/de/debe… · 2026/11/3 2:18:22

如何用lawn搭建你的视频评审工作流?团队设置3步走完全手册
如何用lawn搭建你的视频评审工作流?团队设置3步走完全手册

【免费下载链接】lawn 项目地址: https://gitcode.com/gh_mirrors/law/lawn 点击查看 免费下载 lawn 是一款面向创意团队的开源视频评审平台(Video Review Platform),主打"帧级精准评论 免账号观看链接 $5/月不限席位&quo… · 2026/11/3 2:18:22

仓库越大越乱?一家郑州五金批发商的“仓库翻身仗“
仓库越大越乱?一家郑州五金批发商的“仓库翻身仗“

一、凌晨一点,老板还在仓库里找一颗螺丝郑州某五金行业的老板,曾经有个外号叫"仓库里的夜猫子"。仓库两千平,货架一排接一排,五金工具、机电配件、紧固件加起来三万多个品项。客户一个电话要某个型号的轴承,… · 2026/11/3 2:47:27

html+css基础:30、伸缩盒模型(伸缩项目--伸/缩,flex复合属性,项目排序与单独对齐,案例)
html+css基础:30、伸缩盒模型(伸缩项目--伸/缩,flex复合属性,项目排序与单独对齐,案例)

伸缩盒模型的伸缩项目--伸/缩,flex复合属性,项目排序与单独对齐,案例等:Ⅰ、伸缩盒模型的伸缩项目--伸:flex-grow: 0;其一、代码为:其二、结果为:Ⅱ、伸缩盒模型的伸缩项目--缩:flex… · 2026/11/3 2:47:27

内网安全第一道闸:为什么你的网络总被 “陌生人“ 混进来?
内网安全第一道闸:为什么你的网络总被 “陌生人“ 混进来?

引言 做过网络 / 安全的都知道,内网失守往往不是防火墙没配好,而是门没看住—— 员工把个人手机、无线路由、不知名摄像头偷偷插进交换机,一台带病毒的工作站随意接入,双网卡电脑绕过隔离直连互联网…… 等你发现时,数… · 2026/11/3 2:47:27

FA_CNN卷积神经网络十九---模型选择、欠拟合与过拟合
FA_CNN卷积神经网络十九---模型选择、欠拟合与过拟合

FA:Formular and Algorithm,CNN:Convoluation Neural Network 1. 写在前面:为什么用多项式讲模型选择 在深度学习中,"模型选择"听起来抽象,但它的本质问题非常朴素:给定一份数据&… · 2026/11/3 2:47:27

经验丰富的汽车安装车载手机互联苹果Carplay和华为Hicar哪家专业
经验丰富的汽车安装车载手机互联苹果Carplay和华为Hicar哪家专业

最近好几个朋友都问我,车子上想装个CarPlay或者HiCar,到底去哪里弄靠谱。说真的,我太懂这种纠结了。我自己那台车,当初就是冲着车机互联去的,结果在路边店装完,先是连不上,后来好不容易连上了&a… · 2026/11/3 2:47:27

GitLab 私有化部署太吃内存:只留仓库和合并请求
GitLab 私有化部署太吃内存:只留仓库和合并请求

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 2:46:27

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18

STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码