发布时间:2026/8/26 18:20:21
内存为什么越跑越高?从对象回收讲透内存泄漏的原因与预防 线上服务有一种问题很让人头疼刚启动时一切正常。运行几个小时后内存开始慢慢上涨。再过一段时间接口变慢、GC 变频繁最后甚至直接 OOM。很多时候这背后就是一个很典型的问题内存泄漏。不少人会把内存泄漏简单理解成“程序申请了内存但是忘记释放。”这个说法不能算错但放在 Java 这类有垃圾回收机制的语言里其实还不够准确。更准确地说某些对象已经没有业务价值了但程序中仍然存在引用指向它们导致垃圾回收器认为这些对象还在使用于是一直无法回收。理解了这一点很多内存泄漏问题其实就没那么神秘了。一、先搞清楚什么才算内存泄漏先看一个简单场景。程序创建了一个对象User user new User();此时内存里大概是user ↓ User对象当方法执行结束并且再也没有地方引用这个对象时User对象就变成了“没人认识的对象”。垃圾回收器发现它不可达就可以把这块内存回收掉。正常流程就是创建对象 ↓ 使用对象 ↓ 失去引用 ↓ GC回收但如果对象明明已经不用了却仍然被某个地方引用着全局集合 ↓ User对象垃圾回收器看到还有引用指向它那它应该还有用。于是不会回收。如果这种对象越来越多对象1 对象2 对象3 对象4 …… 对象100000内存就会一点点被吃掉。这才是 Java 里最常见的内存泄漏。二、垃圾回收器到底怎么判断一个对象能不能删很多人会以为没人用了就回收。问题是JVM 怎么知道“没人用了”核心思路叫可达性分析。JVM 会从一批特殊的对象开始向下寻找。这些对象叫GC Roots。可以简单理解成GC Roots │ ├── 线程栈里的变量 ├── 静态变量 ├── JNI引用 └── JVM内部对象然后顺着引用关系往下找GC Root ↓ 对象A ↓ 对象B ↓ 对象C只要能够从 GC Root 一路找到某个对象这个对象就被认为仍然存活。反过来对象X → 对象Y虽然 X 和 Y 互相引用但如果从 GC Root 根本找不到它们GC Root 对象X ↔ 对象Y │ └──── 找不到它们还是可以被 GC 回收。所以 Java 内存泄漏真正的核心不是对象有没有引用。而是对象是不是仍然可以从 GC Root 到达。三、最常见的泄漏集合只进不出这大概是业务代码里最容易出现的一类问题。比如为了做缓存写了一个private static final MapString, User CACHE new HashMap();每次请求都放进去CACHE.put(userId, user);但从来没有删除。开始的时候CACHE ├── User1 ├── User2 └── User3运行久了CACHE ├── User1 ├── User2 ├── User3 ├── User4 ├── User5 ├── ... └── User500000关键在于static变量 ↓ CACHE ↓ User对象静态变量本身可以长期存在。所以这些 User 对象始终可以从 GC Root 找到。GC 就算跑一百次也不会回收它们。这就是一个标准的内存泄漏。这里特别容易出现一种误解“我都用了 GC 语言了怎么还会泄漏”因为 GC 只能帮你回收真正不可达的对象。它没办法理解你的业务逻辑。它不知道这个用户的数据三小时前就没用了它只知道还有引用不能删。四、缓存其实是内存泄漏的高发区很多程序都有缓存数据库查询 ↓ 放入内存 ↓ 下次直接读取本意是减少数据库压力。但如果缓存只写cache.put(key, value);从来不cache.remove(key);也不设置最大容量 过期时间 淘汰策略那缓存迟早会变成一个永远只进不出的仓库。这就像你租了一个仓库。每天往里面放 100 个箱子第一天100 第二天200 第三天300 ……但规定什么都不能扔。那问题根本不是仓库够不够大。而是迟早会满。所以缓存系统一般都会有TTL LRU 最大容量 主动失效这样的机制。比如最多缓存10000条 ↓ 超过后淘汰最久没使用的数据或者缓存30分钟 ↓ 超过30分钟自动删除本质上都是为了主动切断无用对象的引用链。五、监听器和回调也特别容易偷偷泄漏这种问题更加隐蔽。假设有一个事件中心eventBus.register(listener);某个页面或者对象创建时把自己注册进去EventBus ↓ Listener ↓ 业务对象后来这个业务对象已经不用了。按理说应该被回收。但是EventBus还保存着它的 Listener。于是引用链一直存在GC Root ↓ EventBus ↓ Listener ↓ 业务对象结果整个对象都回收不了。如果不断注册 注册 注册 注册却从来没有unregister();内存就会慢慢涨。所以看到register subscribe addListener这类代码时最好顺便想一下对应的 unregister 在哪里 unsubscribe 在哪里 removeListener 在哪里有注册就应该考虑解绑。六、ThreadLocal 为什么也会造成内存泄漏ThreadLocal是 Java 中非常经典的一个坑。比如ThreadLocalUserContext context new ThreadLocal();使用context.set(userContext);正常情况下使用完最好context.remove();为什么因为在线程池里线程不是请求结束就销毁。可能是线程1 ↓ 处理请求A ↓ 处理请求B ↓ 处理请求C ↓ 一直活着ThreadLocal 的数据实际上和线程存在关联。如果请求结束以后没有清理Thread ↓ ThreadLocalMap ↓ Value这个 Value 可能长期留在线程里。普通线程如果很快结束问题还不大。但线程池里的线程可能活几小时 活几天 甚至跟服务一样久这时候问题就来了。所以使用 ThreadLocal 时一个非常实用的写法是try { threadLocal.set(value); // 业务代码 } finally { threadLocal.remove(); }重点就在finally无论业务有没有异常最后都清掉。七、数据库连接、文件流不释放算不算内存泄漏严格来说这类问题更接近资源泄漏。比如Connection connection ... InputStream input ... Socket socket ...如果用完以后不关闭数据库连接 文件句柄 Socket就会一直占着系统资源。最终可能出现连接池耗尽 Too many open files Socket资源不足虽然它和普通 Java 堆内存泄漏不是完全一个概念但在实际开发中经常一起讨论。因为最终表现非常像资源一直占着不归还。所以现在 Java 推荐try (InputStream input ...) { // 使用 }也就是try-with-resources。它能保证资源在作用域结束后自动关闭。比手动input.close();更不容易漏。八、为什么“对象很大”不一定是内存泄漏这个区别也很重要。假设程序一次加载2GB文件然后内存瞬间涨到3GB这不一定是泄漏。如果处理完以后引用消失 ↓ GC执行 ↓ 内存被回收那只是正常的大内存使用。真正的内存泄漏通常更像500MB ↓ 800MB ↓ 1.2GB ↓ 1.8GB ↓ 2.5GB ↓ OOM而且即使经过 Full GC内存还是下不来这时候才非常值得怀疑是不是有什么对象一直被引用着九、还有一种情况不是泄漏而是“对象生产太快”假设程序每秒创建100万个对象GC 虽然一直在回收创建 ↓ 回收 ↓ 创建 ↓ 回收但如果对象产生速度 GC回收速度内存同样会不断上涨。最终也可能 OOM。这种情况不是典型泄漏而是内存分配压力过大。所以看到OutOfMemoryError不能直接下结论一定有内存泄漏还得继续看到底是对象不能回收 还是对象产生速度太快十、线上怎么判断是不是内存泄漏一个很常见的判断思路是先看 GC 之后的内存。比如第一次 Full GC 后500MB 第二次 Full GC 后700MB 第三次 Full GC 后1GB 第四次 Full GC 后1.5GB如果每次垃圾回收以后存活对象还是越来越多。那就比较可疑了。因为这意味着GC明明努力回收了 ↓ 但仍然有大量对象不能删除下一步一般就会分析Heap Dump看看到底什么对象最多 是谁引用了它 为什么一直无法回收最后最重要的往往不是哪个对象占了2GB而是谁把它拽住了也就是寻找引用链GC Root ↓ 对象A ↓ 对象B ↓ 巨大集合 ↓ 几十万个对象找到这条链泄漏原因通常就快出来了。十一、内存泄漏怎么预防比起线上 OOM 后再排查平时写代码时提前防范成本低得多。首先是各种集合。看到static Map static List static Set最好问一句这里的数据什么时候删除如果答案是不知道那就值得警惕。对于缓存一定考虑容量限制 一定考虑过期时间 一定考虑淘汰机制对于监听器注册 ↓ 记得注销对于 ThreadLocalset ↓ try ↓ finally remove对于连接和流申请资源 ↓ 使用 ↓ 及时关闭说到底预防内存泄漏有一个很简单的原则任何生命周期比较长的对象只要它开始保存其他对象的引用就要考虑这些引用什么时候解除。十二、一个很实用的判断方法以后看到一段代码container.add(obj);不要只看add最好顺手问三个问题谁持有 container container 能活多久 obj 什么时候 remove如果答案是container是static ↓ 跟程序活得一样久 ↓ obj从不删除那基本已经闻到内存泄漏的味道了。写在最后内存泄漏其实没有想象中那么玄学。对于 Java 这类带 GC 的语言来说它最核心的逻辑就是一个已经没用的对象因为仍然存在从 GC Root 到它的引用链所以垃圾回收器无法判断它已经“没用”。整个问题可以压缩成业务上已经不用 ↓ 引用却还存在 ↓ 对象仍然可达 ↓ GC无法回收 ↓ 对象越来越多 ↓ 内存持续上涨 ↓ 最终OOM所以真正需要关注的从来不只是创建了多少对象而是这些对象用完以后 还有谁在引用它们缓存没有淘汰、监听器没有解绑、ThreadLocal 没清理、静态集合无限增长本质上其实都是同一件事对象该走的时候没有人把那根引用线剪断。想清楚“对象为什么还活着”基本就抓住了内存泄漏问题的核心。

相关新闻

2026/8/26 18:20:21

Altium Develop 学习资料整理

()引言 Altium Develop 可以理解为 Altium Designer(AD) 与 Altium 365(A365) 能力的结合。它不仅包含传统的原理图设计、PCB 设计和规则检查功能,也进一步结合了工作区、项目协作、版本管理、…

2026/8/26 18:20:21

大疆校园招聘C++开发类笔试题目

客观题 第1题 单选题(4.00分) 题干 某打印服务系统仅配备2台可用打印机接口,使用初值为2的计数信号量S进行资源管理。系统中有5个打印任务线程,其中4个线程几乎同时执行P(S)操作申请打印机接口,此时信号量S的值以及处于…

2026/8/26 19:10:45

16|设计一个AI业务知识分析台:它首先应该解决BA的哪些任务?

食味里“川香鸡腿饭套餐”项目结束后,BA林悦做了一次复盘。她发现自己每天都在不同工具之间搬运同一批业务信息。 企微里是商品、研发、供应链和门店的讨论;录音转写里藏着尚未确认的业务规则;OA附件里有新品审批;Excel里维护产品…

2026/8/26 19:10:45

具身智能TVA-VLA实现前瞻协同交互新突破

前沿技术探索:TVA智能体(简称TVA)TVA智能体(亦称“AI智能体视觉”或“TVA视觉智能体”)是依托Transformer架构与“因式智能体”理论构建的系统级视觉技术框架。它融合深度强化学习(DRL)、卷积神…

2026/8/26 19:10:45

代码里实际能看到的路径 + 注释里的设计意图

下面按代码里实际能看到的路径 + 注释里的设计意图,说明 AutomaticStationPipeline 何时创建、何时销毁,以及中间状态怎么变。 1. 它是什么、挂在哪 类型:AutomaticStationPipeline(站内 5 个 Worker + 5 个 Channel) 持有者:StationControlViewModel 的字段 _automaticP…

2026/8/26 19:10:45

苹果连接热点显示低安全性

1、问题描述开发MIFI过程中发现,苹果手机连接热点显示安全性低,2、问题分析抓MIFI空口分析如下,相关群加密方式为TKIP,但是组加密方式有两个CCMP和TKIP相关群加密方式为CCMP,组加密方式CCMPTKIP、CCMP是两类无线加密协…

2026/8/26 19:05:44

机器学习——Day05

一、K-近邻算法1.K-近邻算法简介1.1什么是K-近邻算法根据你的“邻居”来推断出你的类型是一种分类算法1.2K-近邻算法(KNN)概念定义:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一…

2026/8/26 9:13:28

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/25 11:48:27

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/25 16:56:43

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/26 0:04:32

Python random 模块常用函数详解:从入门到实战

目录 1. 引言2. 准备工作3. 基础随机函数4. 序列相关函数5. 随机种子与复现6. 实战案例7. 注意事项8. 常见问题与排查9. 总结 1. 引言 摘要: 本文系统介绍 Python 标准库 random 模块中最常用的随机数生成函数。内容涵盖基础随机函数(random()、unifor…

2026/8/26 1:19:35

JSON总结

JSON概念 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,主要用于跟服务器进行交换数据。它基于ECMAScript的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C、C、C#、Java、JavaScr…

2026/8/26 1:19:35

保存连接sse 是什么原理,为什么不会一直请求

“保持连接”用的是 SSE(Server-Sent Events),本质是一个没有马上结束的 HTTP 请求。 过程是: 拷贝机发送一次请求: GET /api/code-sync/events服务器返回: Content-Type: text/event-stream但不关闭响应&…

2026/8/24 13:42:17

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/24 18:13:48

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/25 1:08:14

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…