i.MX35 WinCE BSP显示驱动适配实战:从时序解析到源码调试
2026/6/21 20:35:19
周三下午,正在摸鱼,突然钉钉群里炸了:
[告警] 订单服务 POD重启 [告警] 订单服务 POD重启 [告警] 订单服务 POD重启3个Pod连续重启,打开监控一看,内存直接打满然后被K8s杀掉了。
经典的OOM。
领导说:内存不够就加嘛。
我:…加内存治标不治本,得找到根因。
# JVM参数里加上GC日志-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/logs/gc.log等了一个小时,服务挂了,捞出GC日志:
[Full GC (Allocation Failure) 3800M->3750M(4096M), 5.234 secs] [Full GC (Allocation Failure) 3780M->3760M(4096M), 5.567 secs] [Full GC (Allocation Failure) 3790M->3785M(4096M), 6.012 secs]Full GC后内存几乎没释放,说明有内存泄漏,有东西一直占着不放。
在容器里加了个脚本,OOM前自动dump:
# JVM参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/heap.hprof但K8s的OOM Kill太快了,还没来得及dump就被杀了。
换个方式,手动dump:
# 找到Java进程PIDjps# 手动dump(等内存涨到3G左右时执行)jmap -dump:format=b,file=/logs/heap.hprof<pid>dump出来一个3G的文件。
把文件拷到本地,用MAT(Memory Analyzer Tool)打开。
Leak Suspects Report: Problem Suspect 1: 256,789 instances of "com.xxx.OrderDTO" 占用内存:2.1 GB (54%)25万个OrderDTO对象?这订单量也没这么大啊。
点进去看引用链:
java.util.concurrent.ConcurrentHashMap -> com.xxx.cache.LocalCache -> OrderDTO (256789 instances)LocalCache,本地缓存。
// LocalCache.javapublicclassLocalCache{privatestaticfinalMap<String,OrderDTO>cache=newConcurrentHashMap<>();publicstaticvoidput(StringorderId,OrderDTOorder){cache.put(orderId,order);}publicstaticOrderDTOget(StringorderId){returncache.get(orderId);}// 没有remove方法// 没有过期机制// 没有大小限制}经典错误:只往缓存里放,不清理。
一查代码提交记录,是3个月前一个同事为了"优化性能"加的,从那之后这个服务就开始间歇性OOM。
方案一:直接删掉这个缓存(最简单)
方案二:换成带过期的缓存
// 用Caffeine替代privatestaticfinalCache<String,OrderDTO>cache=Caffeine.newBuilder().maximumSize(10000)// 最多1万条.expireAfterWrite(5,TimeUnit.MINUTES)// 5分钟过期.build();publicstaticvoidput(StringorderId,OrderDTOorder){cache.put(orderId,order);}publicstaticOrderDTOget(StringorderId){returncache.getIfPresent(orderId);}上线后,内存稳定在1.5G左右,再也没OOM过。
趁这个机会,把JVM参数也优化了一下。
-Xms2g -Xmx4g# 就这两个参数...# 堆内存-Xms4g -Xmx4g# 初始和最大一样,避免动态调整-XX:NewRatio=2# 年轻代:老年代 = 1:2# GC选择(JDK11+推荐G1)-XX:+UseG1GC -XX:MaxGCPauseMillis=200# 目标停顿时间200ms-XX:G1HeapRegionSize=8m# Region大小# 元空间-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m# GC日志-Xlog:gc*:file=/logs/gc.log:time,level,tags:filecount=5,filesize=100m# OOM时dump-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/# 容器感知(JDK8u191+)-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0# 使用容器内存的75%加了几个关键监控:
# Prometheus指标-jvm_memory_used_bytes-jvm_gc_pause_seconds_sum-jvm_gc_pause_seconds_count-jvm_classes_loaded_classes_total设置告警:
| 场景 | 工具 | 命令 |
|---|---|---|
| 查看堆内存 | jmap | jmap -heap |
| dump内存 | jmap | jmap -dump:format=b,file=heap.hprof |
| 分析dump | MAT | 图形界面 |
| 实时监控 | jstat | jstat -gcutil 1000 |
| 查看线程 | jstack | jstack |
| 在线分析 | Arthas | dashboard/heapdump |
推荐用Arthas,在线诊断特别方便:
# 下载启动curl-O https://arthas.aliyun.com/arthas-boot.jar java -jar arthas-boot.jar# 选择要attach的进程后:# 查看堆内存dashboard# 查看最占内存的对象heapdump --live /tmp/heap.hprof# 查看某个类的实例数sc -d com.xxx.OrderDTO vmtool --action getInstances --className com.xxx.OrderDTO --limit10# 查看方法调用watchcom.xxx.LocalCache put'{params, returnObj}'-x2这次OOM排查还好在公司,能直接连到服务器。
如果在家收到告警,K8s集群在公司内网,怎么办?
之前的方案是远程专线,但经常断,而且手机上操作kubectl很难受。
后来用星空组网把电脑和跳板机组到一起,在家也能kubectl进容器排查了。
这次OOM排查的经验:
| 步骤 | 工具/方法 |
|---|---|
| 1. 确认OOM | 监控告警、GC日志 |
| 2. dump内存 | jmap / HeapDumpOnOutOfMemoryError |
| 3. 分析dump | MAT / jhat |
| 4. 定位代码 | 引用链分析 |
| 5. 修复上线 | 代码修改 |
| 6. 加固监控 | JVM监控指标 |
常见的内存泄漏原因:
最后,写缓存的时候一定要想清楚:
不然就等着半夜被电话叫醒吧。
有JVM相关问题欢迎评论区交流。