生产环境 OOM 排障复盘一个实习生遇到的真实内存泄漏一、凌晨两点生产告警Pod OOMKilled半夜收到告警服务 Pod 因为内存超过限制被 K8s 杀掉了。重启后恢复正常但一小时后再次 OOM。这是一个典型的渐进式内存泄漏——不是一次分配太多内存而是随时间推移内存慢慢被蚕食。二、OOM 排障路线图实战踩坑记录我们第一次遇到 OOM 时花了 2 天才定位到问题。当时服务运行 24 小时后内存从 1GB 涨到 8GBK8s 每 6 小时杀一次 Pod。flowchart TD A[OOM 告警] -- B[确认是内存泄漏还是正常峰值] B -- C[检查 JVM/进程内存趋势] C -- C1{内存持续增长?} C1 --|是| D[内存泄漏] C1 --|否| E[正常业务峰值: 扩容或优化] D -- F[获取 Heap Dump] F -- G[分析大对象引用链] G -- H{泄漏对象类型} H --|HashMap 持续增长| I[缓存未设置上限] H --|ThreadLocal 未清理| J[线程池中的 ThreadLocal 残留] H --|连接未关闭| K[数据库/HTTP 连接池泄漏] H --|大对象频繁创建| L[字符串拼接或大数组] I -- M[修复 验证] J -- M K -- M L -- M生产级排查步骤第一步确认是否是内存泄漏# 查看 Pod 内存使用趋势Prometheus 查询 sum(container_memory_working_set_bytes{pod~your-pod-.*}) by (pod) # 如果内存持续上升不下降 → 泄漏 # 如果内存上升到某点后稳定 → 可能是正常缓存第二步获取内存快照# 对于 JVM 应用 jmap -dump:live,formatb,fileheap.hprof pid # 对于 Python 应用 pip install memory_profiler python -m memory_profiler your_script.py # 对于 Go 应用 curl http://localhost:6060/debug/pprof/heap heap.prof第三步分析大对象# JVM: 使用 Eclipse MAT 或 jhat jhat heap.hprof # Python: 使用 objgraph import objgraph objgraph.show_most_common_types(limit20) # Go: 使用 pprof go tool pprof heap.prof (pprof) top (pprof) list suspicious_function关键认知OOM 不是内存突然不够用而是内存被慢慢蚕食直到临界点。排查的关键是趋势分析而不是只看当前内存使用率。三、常见泄漏模式与代码示例 场景 1缓存未设置上限最常见的内存泄漏 一个用于加速查询的本地缓存由于没有容量上限 随时间推移无限增长最终 OOM。 # 问题代码 class UnlimitedCache: 没有容量上限的缓存 内存泄漏 def __init__(self): self._data {} # 只增不减内存持续增长 def get(self, key): return self._data.get(key) def set(self, key, value): self._data[key] value # 永远不会淘汰旧数据 # 修复代码 from collections import OrderedDict class LRUCache: 带容量限制的 LRU 缓存 def __init__(self, max_size: int 10000): self._data OrderedDict() self._max_size max_size def get(self, key): if key in self._data: # 访问时移到末尾最近使用 self._data.move_to_end(key) return self._data[key] return None def set(self, key, value): if key in self._data: self._data.move_to_end(key) self._data[key] value # 超过容量时淘汰最久未使用的 if len(self._data) self._max_size: self._data.popitem(lastFalse) 场景 2ThreadLocal 未清理 在线程池环境中线程被复用ThreadLocal 中的引用 不会被 GC 回收造成内存泄漏。 import threading # 问题代码 class UserContext: _thread_local threading.local() classmethod def set_user(cls, user): cls._thread_local.user user # 设置后从未清理 # 修复代码 class UserContextFixed: _thread_local threading.local() classmethod def set_user(cls, user): cls._thread_local.user user classmethod def clear(cls): 请求结束后必须调用此方法清理 if hasattr(cls._thread_local, user): del cls._thread_local.user 场景 3大列表累积流式数据处理误区 # 问题代码处理大文件时把所有行加载到内存 def process_file_bad(filepath): lines [] with open(filepath) as f: for line in f: lines.append(line) # 100G 文件 → OOM return process(lines) # 修复代码流式处理 def process_file_good(filepath): results [] with open(filepath) as f: for line in f: result process_line(line) results.append(result) # 定期刷盘或限制内存中的结果数量 if len(results) 10000: flush_to_disk(results) results.clear() if results: flush_to_disk(results)三、常见泄漏模式与代码示例生产级踩坑记录我们在一个数据处理服务中遇到过类似问题。服务需要处理 10GB 的日志文件最初的实现是一次性读取所有行到内存导致内存峰值16GB处理时间2小时OOM 频率每3天1次改为流式处理后内存峰值200MB降低 98%处理时间2.5小时增加 25% 时间但内存安全OOM 频率0关键经验处理大文件必须流式读取定期主动释放内存list.clear()或del使用生成器yield减少内存占用设置内存使用上限告警如 80% 时介入一个用于加速查询的本地缓存由于没有容量上限随时间推移无限增长最终 OOM。问题代码class UnlimitedCache:没有容量上限的缓存 内存泄漏definit(self):self._data {} # 只增不减内存持续增长def get(self, key): return self._data.get(key) def set(self, key, value): self._data[key] value # 永远不会淘汰旧数据修复代码from collections import OrderedDictclass LRUCache:带容量限制的 LRU 缓存definit(self, max_size: int 10000):self._data OrderedDict()self._max_size max_sizedef get(self, key): if key in self._data: # 访问时移到末尾最近使用 self._data.move_to_end(key) return self._data[key] return None def set(self, key, value): if key in self._data: self._data.move_to_end(key) self._data[key] value # 超过容量时淘汰最久未使用的 if len(self._data) self._max_size: self._data.popitem(lastFalse)场景 2ThreadLocal 未清理在线程池环境中线程被复用ThreadLocal 中的引用不会被 GC 回收造成内存泄漏。import threading问题代码class UserContext:_thread_local threading.local()classmethod def set_user(cls, user): cls._thread_local.user user # 设置后从未清理修复代码class UserContextFixed:_thread_local threading.local()classmethod def set_user(cls, user): cls._thread_local.user user classmethod def clear(cls): 请求结束后必须调用此方法清理 if hasattr(cls._thread_local, user): del cls._thread_local.user场景 3大列表累积流式数据处理误区问题代码处理大文件时把所有行加载到内存def process_file_bad(filepath):lines []with open(filepath) as f:for line in f:lines.append(line) # 100G 文件 → OOMreturn process(lines)修复代码流式处理def process_file_good(filepath):results []with open(filepath) as f:for line in f:result process_line(line)results.append(result)# 定期刷盘或限制内存中的结果数量if len(results) 10000:flush_to_disk(results)results.clear()if results:flush_to_disk(results)## 四、内存监控与告警 python import psutil import os import threading import time class MemoryMonitor: 内存监控器在接近 OOM 前主动告警 def __init__(self, warning_threshold0.75, critical_threshold0.90, check_interval30): self.warning warning_threshold self.critical critical_threshold self.interval check_interval self._process psutil.Process(os.getpid()) self._running False def start(self): 启动后台监控线程 self._running True thread threading.Thread(targetself._monitor_loop, daemonTrue) thread.start() def stop(self): self._running False def _monitor_loop(self): 监控循环 consecutive_warnings 0 while self._running: mem_info self._process.memory_info() usage_gb mem_info.rss / (1024 ** 3) total_gb psutil.virtual_memory().total / (1024 ** 3) usage_pct mem_info.rss / psutil.virtual_memory().total if usage_pct self.critical: print(f[CRITICAL] 内存使用 {usage_gb:.1f}GB / {total_gb:.1f}GB ({usage_pct:.1%}), 建议立即排查) consecutive_warnings 1 if consecutive_warnings 5: self._dump_memory_info() elif usage_pct self.warning: print(f[WARNING] 内存使用 {usage_gb:.1f}GB ({usage_pct:.1%}), 持续上升趋势) consecutive_warnings 1 else: consecutive_warnings 0 time.sleep(self.interval) def _dump_memory_info(self): 导出内存快照信息 pass # 生产环境集成专门的 profiler生产环境建议设置分层告警75% 警告90% 严重告警95% 立即介入import psutil import os import threading import time class MemoryMonitor: 内存监控器在接近 OOM 前主动告警 设计思路 不用等到 OOM 才去排查而是在内存使用率持续上升时 提前告警争取在 OOM 前介入。 def __init__( self, warning_threshold: float 0.75, # 75% 告警 critical_threshold: float 0.90, # 90% 严重告警 check_interval: int 30, # 检查间隔秒 ): self.warning warning_threshold self.critical critical_threshold self.interval check_interval self._process psutil.Process(os.getpid()) self._running False def start(self) - None: 启动后台监控线程 self._running True thread threading.Thread(targetself._monitor_loop, daemonTrue) thread.start() def stop(self) - None: self._running False def _monitor_loop(self) - None: 监控循环 consecutive_warnings 0 while self._running: mem_info self._process.memory_info() # RSS: 实际物理内存使用 usage_gb mem_info.rss / (1024 ** 3) # 获取系统总内存计算百分比 total_gb psutil.virtual_memory().total / (1024 ** 3) usage_pct mem_info.rss / psutil.virtual_memory().total if usage_pct self.critical: print( f[CRITICAL] 内存使用 {usage_gb:.1f}GB / {total_gb:.1f}GB f({usage_pct:.1%}), 建议立即排查 ) consecutive_warnings 1 if consecutive_warnings 5: # 连续 5 次严重告警主动 dump 堆信息 self._dump_memory_info() elif usage_pct self.warning: print( f[WARNING] 内存使用 {usage_gb:.1f}GB ({usage_pct:.1%}), f持续上升趋势 ) consecutive_warnings 1 else: consecutive_warnings 0 time.sleep(self.interval) def _dump_memory_info(self) - None: 导出内存快照信息 # 生产环境集成专门的 profiler pass五、经验总结内存泄漏排查清单看趋势内存是持续增长还是间歇性峰值前者是泄漏后者可能是业务特征。看对象Heap Dump 中占比最大的是什么对象它的引用链是什么看缓存缓存是否设置了容量上限和过期时间看连接数据库连接、HTTP 连接是否被正确关闭和归还看线程ThreadLocal 在线程池环境中是否被清理最重要的一条经验是OOM 不是问题本身是问题最后的表现。在它之前一定有内存使用率持续上升的征兆。建立内存监控在 75% 告警时介入比在 100% OOM 时救火要好得多。
生产环境 OOM 排障复盘:一个实习生遇到的真实内存泄漏
发布时间:2026/7/11 21:09:24
生产环境 OOM 排障复盘一个实习生遇到的真实内存泄漏一、凌晨两点生产告警Pod OOMKilled半夜收到告警服务 Pod 因为内存超过限制被 K8s 杀掉了。重启后恢复正常但一小时后再次 OOM。这是一个典型的渐进式内存泄漏——不是一次分配太多内存而是随时间推移内存慢慢被蚕食。二、OOM 排障路线图实战踩坑记录我们第一次遇到 OOM 时花了 2 天才定位到问题。当时服务运行 24 小时后内存从 1GB 涨到 8GBK8s 每 6 小时杀一次 Pod。flowchart TD A[OOM 告警] -- B[确认是内存泄漏还是正常峰值] B -- C[检查 JVM/进程内存趋势] C -- C1{内存持续增长?} C1 --|是| D[内存泄漏] C1 --|否| E[正常业务峰值: 扩容或优化] D -- F[获取 Heap Dump] F -- G[分析大对象引用链] G -- H{泄漏对象类型} H --|HashMap 持续增长| I[缓存未设置上限] H --|ThreadLocal 未清理| J[线程池中的 ThreadLocal 残留] H --|连接未关闭| K[数据库/HTTP 连接池泄漏] H --|大对象频繁创建| L[字符串拼接或大数组] I -- M[修复 验证] J -- M K -- M L -- M生产级排查步骤第一步确认是否是内存泄漏# 查看 Pod 内存使用趋势Prometheus 查询 sum(container_memory_working_set_bytes{pod~your-pod-.*}) by (pod) # 如果内存持续上升不下降 → 泄漏 # 如果内存上升到某点后稳定 → 可能是正常缓存第二步获取内存快照# 对于 JVM 应用 jmap -dump:live,formatb,fileheap.hprof pid # 对于 Python 应用 pip install memory_profiler python -m memory_profiler your_script.py # 对于 Go 应用 curl http://localhost:6060/debug/pprof/heap heap.prof第三步分析大对象# JVM: 使用 Eclipse MAT 或 jhat jhat heap.hprof # Python: 使用 objgraph import objgraph objgraph.show_most_common_types(limit20) # Go: 使用 pprof go tool pprof heap.prof (pprof) top (pprof) list suspicious_function关键认知OOM 不是内存突然不够用而是内存被慢慢蚕食直到临界点。排查的关键是趋势分析而不是只看当前内存使用率。三、常见泄漏模式与代码示例 场景 1缓存未设置上限最常见的内存泄漏 一个用于加速查询的本地缓存由于没有容量上限 随时间推移无限增长最终 OOM。 # 问题代码 class UnlimitedCache: 没有容量上限的缓存 内存泄漏 def __init__(self): self._data {} # 只增不减内存持续增长 def get(self, key): return self._data.get(key) def set(self, key, value): self._data[key] value # 永远不会淘汰旧数据 # 修复代码 from collections import OrderedDict class LRUCache: 带容量限制的 LRU 缓存 def __init__(self, max_size: int 10000): self._data OrderedDict() self._max_size max_size def get(self, key): if key in self._data: # 访问时移到末尾最近使用 self._data.move_to_end(key) return self._data[key] return None def set(self, key, value): if key in self._data: self._data.move_to_end(key) self._data[key] value # 超过容量时淘汰最久未使用的 if len(self._data) self._max_size: self._data.popitem(lastFalse) 场景 2ThreadLocal 未清理 在线程池环境中线程被复用ThreadLocal 中的引用 不会被 GC 回收造成内存泄漏。 import threading # 问题代码 class UserContext: _thread_local threading.local() classmethod def set_user(cls, user): cls._thread_local.user user # 设置后从未清理 # 修复代码 class UserContextFixed: _thread_local threading.local() classmethod def set_user(cls, user): cls._thread_local.user user classmethod def clear(cls): 请求结束后必须调用此方法清理 if hasattr(cls._thread_local, user): del cls._thread_local.user 场景 3大列表累积流式数据处理误区 # 问题代码处理大文件时把所有行加载到内存 def process_file_bad(filepath): lines [] with open(filepath) as f: for line in f: lines.append(line) # 100G 文件 → OOM return process(lines) # 修复代码流式处理 def process_file_good(filepath): results [] with open(filepath) as f: for line in f: result process_line(line) results.append(result) # 定期刷盘或限制内存中的结果数量 if len(results) 10000: flush_to_disk(results) results.clear() if results: flush_to_disk(results)三、常见泄漏模式与代码示例生产级踩坑记录我们在一个数据处理服务中遇到过类似问题。服务需要处理 10GB 的日志文件最初的实现是一次性读取所有行到内存导致内存峰值16GB处理时间2小时OOM 频率每3天1次改为流式处理后内存峰值200MB降低 98%处理时间2.5小时增加 25% 时间但内存安全OOM 频率0关键经验处理大文件必须流式读取定期主动释放内存list.clear()或del使用生成器yield减少内存占用设置内存使用上限告警如 80% 时介入一个用于加速查询的本地缓存由于没有容量上限随时间推移无限增长最终 OOM。问题代码class UnlimitedCache:没有容量上限的缓存 内存泄漏definit(self):self._data {} # 只增不减内存持续增长def get(self, key): return self._data.get(key) def set(self, key, value): self._data[key] value # 永远不会淘汰旧数据修复代码from collections import OrderedDictclass LRUCache:带容量限制的 LRU 缓存definit(self, max_size: int 10000):self._data OrderedDict()self._max_size max_sizedef get(self, key): if key in self._data: # 访问时移到末尾最近使用 self._data.move_to_end(key) return self._data[key] return None def set(self, key, value): if key in self._data: self._data.move_to_end(key) self._data[key] value # 超过容量时淘汰最久未使用的 if len(self._data) self._max_size: self._data.popitem(lastFalse)场景 2ThreadLocal 未清理在线程池环境中线程被复用ThreadLocal 中的引用不会被 GC 回收造成内存泄漏。import threading问题代码class UserContext:_thread_local threading.local()classmethod def set_user(cls, user): cls._thread_local.user user # 设置后从未清理修复代码class UserContextFixed:_thread_local threading.local()classmethod def set_user(cls, user): cls._thread_local.user user classmethod def clear(cls): 请求结束后必须调用此方法清理 if hasattr(cls._thread_local, user): del cls._thread_local.user场景 3大列表累积流式数据处理误区问题代码处理大文件时把所有行加载到内存def process_file_bad(filepath):lines []with open(filepath) as f:for line in f:lines.append(line) # 100G 文件 → OOMreturn process(lines)修复代码流式处理def process_file_good(filepath):results []with open(filepath) as f:for line in f:result process_line(line)results.append(result)# 定期刷盘或限制内存中的结果数量if len(results) 10000:flush_to_disk(results)results.clear()if results:flush_to_disk(results)## 四、内存监控与告警 python import psutil import os import threading import time class MemoryMonitor: 内存监控器在接近 OOM 前主动告警 def __init__(self, warning_threshold0.75, critical_threshold0.90, check_interval30): self.warning warning_threshold self.critical critical_threshold self.interval check_interval self._process psutil.Process(os.getpid()) self._running False def start(self): 启动后台监控线程 self._running True thread threading.Thread(targetself._monitor_loop, daemonTrue) thread.start() def stop(self): self._running False def _monitor_loop(self): 监控循环 consecutive_warnings 0 while self._running: mem_info self._process.memory_info() usage_gb mem_info.rss / (1024 ** 3) total_gb psutil.virtual_memory().total / (1024 ** 3) usage_pct mem_info.rss / psutil.virtual_memory().total if usage_pct self.critical: print(f[CRITICAL] 内存使用 {usage_gb:.1f}GB / {total_gb:.1f}GB ({usage_pct:.1%}), 建议立即排查) consecutive_warnings 1 if consecutive_warnings 5: self._dump_memory_info() elif usage_pct self.warning: print(f[WARNING] 内存使用 {usage_gb:.1f}GB ({usage_pct:.1%}), 持续上升趋势) consecutive_warnings 1 else: consecutive_warnings 0 time.sleep(self.interval) def _dump_memory_info(self): 导出内存快照信息 pass # 生产环境集成专门的 profiler生产环境建议设置分层告警75% 警告90% 严重告警95% 立即介入import psutil import os import threading import time class MemoryMonitor: 内存监控器在接近 OOM 前主动告警 设计思路 不用等到 OOM 才去排查而是在内存使用率持续上升时 提前告警争取在 OOM 前介入。 def __init__( self, warning_threshold: float 0.75, # 75% 告警 critical_threshold: float 0.90, # 90% 严重告警 check_interval: int 30, # 检查间隔秒 ): self.warning warning_threshold self.critical critical_threshold self.interval check_interval self._process psutil.Process(os.getpid()) self._running False def start(self) - None: 启动后台监控线程 self._running True thread threading.Thread(targetself._monitor_loop, daemonTrue) thread.start() def stop(self) - None: self._running False def _monitor_loop(self) - None: 监控循环 consecutive_warnings 0 while self._running: mem_info self._process.memory_info() # RSS: 实际物理内存使用 usage_gb mem_info.rss / (1024 ** 3) # 获取系统总内存计算百分比 total_gb psutil.virtual_memory().total / (1024 ** 3) usage_pct mem_info.rss / psutil.virtual_memory().total if usage_pct self.critical: print( f[CRITICAL] 内存使用 {usage_gb:.1f}GB / {total_gb:.1f}GB f({usage_pct:.1%}), 建议立即排查 ) consecutive_warnings 1 if consecutive_warnings 5: # 连续 5 次严重告警主动 dump 堆信息 self._dump_memory_info() elif usage_pct self.warning: print( f[WARNING] 内存使用 {usage_gb:.1f}GB ({usage_pct:.1%}), f持续上升趋势 ) consecutive_warnings 1 else: consecutive_warnings 0 time.sleep(self.interval) def _dump_memory_info(self) - None: 导出内存快照信息 # 生产环境集成专门的 profiler pass五、经验总结内存泄漏排查清单看趋势内存是持续增长还是间歇性峰值前者是泄漏后者可能是业务特征。看对象Heap Dump 中占比最大的是什么对象它的引用链是什么看缓存缓存是否设置了容量上限和过期时间看连接数据库连接、HTTP 连接是否被正确关闭和归还看线程ThreadLocal 在线程池环境中是否被清理最重要的一条经验是OOM 不是问题本身是问题最后的表现。在它之前一定有内存使用率持续上升的征兆。建立内存监控在 75% 告警时介入比在 100% OOM 时救火要好得多。