
凌晨两点手机炸响告警短信一条接一条。我眯着眼打开电脑看到服务节点一个接一个挂掉日志里赫然写着java.lang.OutOfMemoryError: Java heap space。那一刻困意全消后背发凉。重启当然可以但我知道重启能解决一时但永远不知道下一次什么时候来。别急着重启先留现场运维同事催我赶紧重启恢复服务我咬牙说了句“再等三分钟”。登上前置机执行jmap -dump:formatb,fileheap.hprof pid把堆内存快照拽下来。三分钟后服务重启用户恢复了可我知道真正的仗才刚开始。线上OOM最怕的不是崩是崩了之后你什么线索都没留下。提前在启动参数里加上-XX:HeapDumpOnOutOfMemoryError关键时刻能救命。MAT里藏着吃内存的巨兽把dump文件拖进Eclipse MAT支配树一展开一个HashMap占用了将近两个G。点进去看里面全是用户会话对象三十多万个每个都带着完整的订单列表和商品详情。正常会话顶多存个用户ID和token谁往里塞这么多东西顺着引用链往上找原来是半年前一个同事为了“减少数据库查询”把整个订单历史塞进了session。OOM从来不是内存不够是代码在某个角落悄悄漏。调优不是调参数是调代码很多人一遇OOM就加堆内存-Xmx从2G调到8G调完确实不报了但两个月后又炸了。为什么因为代码还在漏你只是把水库修大了上游洪水照来。我把那个HashMap改成只存必要字段订单历史按需查询堆内存反而降到1.5G就稳了。JVM参数是锦上添花代码才是雪中送炭。不修代码只调参数等于给漏水的桶换个大号。堆内存分代心里要有数新生代放朝生夕死的对象老年代放活得久的。对象在新生代熬过几次GC就晋升老年代晋升太快会把老年代撑爆。我这次的问题就是大量会话对象直接进了老年代因为新生代太小-Xmn只给了256M。调成1G后大部分对象在新生代就被回收了。不懂分代就像不知道水库什么时候泄洪早晚被淹。GC日志是最好的老师开启-Xlog:gc之后日志里写得清清楚楚Full GC每十分钟一次每次停顿三秒。三秒看着不长高并发下足够拖垮整个链路。分析后发现老年代回收效率极低因为大部分对象都是活的GC白忙活。结合MAT的结论根源还是代码把长生命周期对象和短生命周期对象混在一起。不看GC日志调优等于闭眼开车。每一次OOM都是学费事故复盘会上我把dump文件、GC日志、代码修改记录摆了一桌。团队定了三条规矩会话对象只存ID大查询必须分页上线前压测必须跑满四小时。后来再没出过OOM。每一次OOM都是学费别交两次。JVM调优不是背参数是理解对象怎么生、怎么死、怎么老去。懂了这些参数自然知道怎么设。线上问题是最好的老师前提是你肯蹲下来把现场一寸一寸看完。