《从一个 OOM 异常开始:我重新理解了 JVM 内存结构与垃圾回收》

《从一个 OOM 异常开始:我重新理解了 JVM 内存结构与垃圾回收》 Java基础 / JVM一、起因写秒杀项目的时候我为了压测用 JMeter 跑了一波高并发。跑着跑着控制台突然红了textjava.lang.OutOfMemoryError: Java heap space当时第一反应是完了内存泄漏了。但冷静下来一想其实这是一个学习 JVM 的好机会。二、我先复习了一下 JVM 内存结构我重新梳理了 JDK 8 的 JVM 内存区域区域作用是否线程私有堆Heap存对象实例、数组共享虚拟机栈存局部变量、方法调用私有本地方法栈调用 native 方法私有方法区元空间存类信息、常量、静态变量共享程序计数器记录字节码行号私有我的 OOM 发生在Heap 区说明堆内存不够用了。三、我做了什么排查加了 JVM 启动参数text-Xms256m -Xmx512m -XX:PrintGCDetails -XX:HeapDumpOnOutOfMemoryError复现问题后用 JVisualVM 看内存发现char[]和String对象占了大量内存。反查代码原来我在日志打印时一次性把一个很大的 List 转成了 Stringjavalog.info(查询结果{}, JSON.toJSONString(largeList));在高并发下多个请求同时创建大字符串直接把堆撑爆了。四、顺便复习了一下垃圾回收排查过程中我重新学习了 GC1. 判断对象是否可回收引用计数法有循环引用问题JVM 不用可达性分析算法GC Root栈中引用、静态属性、常量引用等2. 常见垃圾回收算法算法原理优点缺点标记-清除标记存活对象清除其余简单内存碎片复制分两块一块用完复制到另一块无碎片内存利用率 50%标记-整理标记存活对象向一端移动无碎片耗时3. 分代收集Java 8 默认新生代复制算法Minor GC老年代标记-清除 / 标记-整理Major GC / Full GC五、我的修复方案不是调参而是改代码java// 改前 log.info(查询结果{}, JSON.toJSONString(largeList)); // 改后 log.info(查询结果条数{}, largeList.size());再加上不在高并发路径上打印大对象使用 StringBuilder 替代 String 拼接之后 OOM 没有再出现。六、一点感悟很多人背 JVM 八股文背得很熟但没有亲手 OOM 过一次永远不知道 GC 日志怎么看。这次踩坑让我真正理解了堆是干什么的GC 什么时候触发代码怎么写才能“对 GC 友好”