Kubernetes Pod 频繁重启排查:一次 OOMKilled 的深度诊断
Debugging Frequent Kubernetes Pod Restarts: A Deep Dive into OOMKilled
| Kevin | 2026-08-26T20:54:12
生产环境有个服务每隔几小时就重启一次,kubectl describe pod 显示 OOMKilled。排查下来发现是一个不起眼的缓存没设上限导致的内存泄漏。
A production service kept restarting every few hours with OOMKilled status. Root cause was a memory leak from an unbounded in-memory cache.
上周有个服务在生产环境每隔 4-6 小时就重启一次,kubectl describe pod 里看到的 Last State 是 OOMKilled。容器的内存限制设的 512MB,按理说应该够用的。 排查过程 1. 确认是内存问题 先看了一下 Grafana 上这个 Pod 的内存监控,果然是一条稳步上升的斜线,从启动时的 120MB 一直涨到 512MB 然后被 kill。典型的内存泄漏特征。 2. 本地复现 在本地用 -Xmx256m 跑了一下,模拟内存受限的环境。同时用 VisualVM 接上去看堆内存变化。跑了大概 2 小时,确认内存在持续增长。 3. Heap Dump 分析 在内存涨到差不多的时候 dump 了堆内存: jmap -dump:format=b,file=heapdump.hprof $(pgrep java) 用 Eclipse MAT 打开分析,发现有一个 HashMap 占了 180MB,里面存了 200 多万个 entry。 4. 定位代码 顺着 MAT 的引用链找到了问题代码——一个本地缓存: // 问题代码 private static final Map<String, UserProfile> cache = new HashMap<>(); public UserProfile getUserProfile(String userId) { return cache.computeIfAbsent(userId, id -> userService.queryFromDB(id)); } 这个缓存只进不出,每来一个新用户就往里塞一条记录,永远不会清理。随着时间推移内存就一直涨。 修复方案 用 Caffeine 替换了这个 HashMap: private static final Cache<String, UserProfile> cache = Caffeine.newBuilder() .maximumSize(10000) .expireAfterWrite(Duration.ofMinutes(30)) .build(); public UserProfile getUserProfile(String userId) { return cache.get(userId, id -> userService.queryFromDB(id)); } 设置了最大容量 10000 条和 30 分钟过期,内存峰值稳定在 200MB 左右,再也没触发过 OOM。 经验总结 以后写本地缓存一定要设上限和过期策略,裸 HashMap 做缓存就是定时炸弹。另外 K8s 的 resource limits 一定要设合理,不要图省事设太大,适当的限制反而能更早暴露问题。
A production service was restarting every 4-6 hours with OOMKilled status. Container memory limit was 512MB which should have been sufficient. Investigation Grafana showed a steady memory climb from 120MB to 512MB - classic memory leak. Heap dump analysis with Eclipse MAT revealed a HashMap consuming 180MB with 2 million entries. Root Cause An unbounded local cache using a plain HashMap that only added entries but never removed them. Every new user added a permanent entry. Fix Replaced with Caffeine cache with 10K max size and 30-minute TTL. Memory stabilized at ~200MB.