ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JVM 遇到的问题-1.0

JVM 遇到的问题-1.0 JVM 运行时数据区JVM 运行时数据区各区域内存特征、OOM 场景、复现要点1. 程序计数器程序计数器 Program Counter Register核心原理线程私有仅存储当前线程正在执行的字节码指令行号是 JVM 最小的内存区域。核心结论唯一无 OOM、无内存溢出的运行时数据区JVM 规范明确规定该区域无需垃圾回收、不会出现内存溢出问题。无复现案例、无故障场景为高频面试判断题考点。存储内容当前线程正在执行的字节码指令行号。异常不会发生 OOM考点JVM 唯一无 OOM 的运行时区域。2. Java 虚拟机栈线程私有Java 虚拟机栈 Java Virtual Machine Stack核心原理线程私有每个线程对应一个专属虚拟机栈方法调用时创建栈帧存储局部变量、操作数栈、方法返回地址等。该区域存在两种异常严格区分栈溢出和内存溢出。场景1StackOverflowError栈深度超限触发原因单个线程方法递归调用过深、方法嵌套层级过多、单个栈帧局部变量过大。复现代码java// 栈深度溢出复现public class StackOverflowDemo {static void recursion() {// 无限递归不断创建栈帧recursion();}public static void main(String[] args) {recursion();}}配套参数-Xss128k缩小单个线程栈大小快速复现报错特征java.lang.StackOverflowError场景2虚拟机栈 OOM线程数量超限触发原因JVM 每个线程都会分配独立栈内存无限创建线程会耗尽操作系统进程内存、触发系统线程数上限无法分配新栈内存。复现代码java// 线程过多导致栈OOM复现public class StackThreadOomDemo {public static void main(String[] args) {while (true) {new Thread(() - {try {// 线程长期存活不销毁Thread.sleep(Integer.MAX_VALUE);} catch (Exception e) {e.printStackTrace();}}).start();}}}报错特征java.lang.OutOfMemoryError: unable to create new native thread核心注意绝大多数人误区栈的 OOM 不是递归太深而是线程数量过多耗尽系统资源。存储内容栈帧、局部变量表、操作数栈、方法返回地址等。异常必须严格区分StackOverflowError触发方法递归深度过大、单个栈帧过大复现无限递归调用参数可加-Xss调整单个线程栈大小。java.lang.OutOfMemoryError: unable to create new native thread触发循环创建大量线程操作系统无法分配栈内存。⚠️注意很多人误区虚拟机栈 OOM 是栈深度溢出。栈深度超限是 StackOverflow大量线程耗尽系统内存才是真正栈相关 OOM。3. 本地方法栈本地方法栈 Native Method Stack核心原理线程私有为 JNI 本地 Native 方法提供栈空间机制、异常类型和 Java 虚拟机栈完全一致。OOM/异常场景Native 方法无限递归 →StackOverflowError大量线程执行 Native 方法持续创建线程 →unable to create new native threadOOM注意事项纯 Java 代码无法直接复现依赖自定义 JNI 本地库面试掌握理论场景即可线上极少单独出现该区域故障。4. Java 堆线程共享Java 堆 Heap最常见 OOM核心原理堆是 JVM 最大的内存区域线程共享专门存放所有 Java 对象实例、数组。只要存在持续创建对象、且 GC 无法及时回收的情况就会触发堆内存溢出。OOM 产生场景内存溢出合理场景业务一次性创建超大对象、高并发瞬时大量对象堆内存上限不足以承载。内存泄漏代码问题对象被强引用长期持有无主动释放逻辑GC 永远无法回收。典型场景静态集合无限缓存、ThreadLocal 未移除、监听器/定时器未注销、全局集合存储业务数据无淘汰策略。可直接运行复现代码javaimport java.util.ArrayList;import java.util.List;// 堆内存溢出 OOM 复现public class HeapOomDemo {public static void main(String[] args) {// 全局集合强持有对象GC无法回收ListObject cacheList new ArrayList();while (true) {// 每次创建1MB字节数组持续占用堆内存cacheList.add(new byte[1024 * 1024]);}}}配套 JVM 启动参数1m 测试,生产环境一般是 2g 起步plain text-Xms1m -Xmx1m -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPathheap-oom.hprof -XX:PrintGCDetails -Xloggc:gc.log报错特征java.lang.OutOfMemoryError: Java heap space排查要点通过 hprof 快照使用 MAT 分析大对象、对象引用链区分是内存不足调优参数还是内存泄漏修复代码。存储内容所有对象实例、数组。OOM 错误java.lang.OutOfMemoryError: Java heap space触发场景① 内存溢出一次性创建超大对象② 内存泄漏对象被强引用持续持有GC 无法回收。复现方案循环创建大 byte 数组JVM 参数设置固定堆-Xms1m -Xmx1m。典型业务泄漏场景静态集合无限缓存、ThreadLocal 不 remove、监听器未注销。5. 方法区JDK8元空间 Metaspace元空间 MetaspaceJDK8 方法区实现核心原理线程共享使用操作系统本地内存堆外内存专门存储类元数据Class 结构体、方法信息、运行时常量池、注解、接口信息等替代 JDK7 及之前的永久代。OOM 产生场景程序动态批量生成、加载大量类CGLIB 动态代理、ASM 字节码增强、Groovy/Shell 脚本动态编译项目热加载、插件化架构、动态路由频繁创建新类限制元空间最大内存后类元数据总量超出上限。可直接运行复现代码CGLIB 动态生成类需引入 CGLIB 依赖xmldependencygroupIdcglib/groupIdartifactIdcglib/artifactIdversion3.3.0/version/dependencyjavaimport net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.NoOp;// 元空间OOM复现public class MetaspaceOomDemo {static class TestClass {}public static void main(String[] args) {while (true) {// 无限动态创建代理类持续占用元空间Enhancer enhancer new Enhancer();enhancer.setSuperclass(TestClass.class);enhancer.setCallback(NoOp.INSTANCE);enhancer.create();}}}配套 JVM 启动参数plain text-XX:MaxMetaspaceSize20m -XX:HeapDumpOnOutOfMemoryError报错特征java.lang.OutOfMemoryError: Metaspace生产坑点不设置MaxMetaspaceSize时元空间无上限会持续占用服务器物理内存最终导致整机内存溢出。存储内容类结构信息、运行时常量池、方法字节码、注解。OOM 错误java.lang.OutOfMemoryError: Metaspace复现无限循环创建 CGLIB 代理类启动参数限制-XX:MaxMetaspaceSize。触发场景程序动态持续生成大量 ClassCGLIB 动态代理、ASM、脚本引擎、热加载、插件化。6. 直接内存堆外内存非运行时数据区直接内存 Direct Memory堆外内存非标准运行时数据区核心原理不属于 JVM 规范定义的五大运行时数据区通过 Unsafe 直接向操作系统申请堆外内存不受-Xmx/-Xms堆参数管控主要用于 NIO、Netty 网络缓冲区实现零拷贝优化 IO 性能。OOM 产生场景频繁分配 DirectByteBuffer 堆外缓冲区无手动释放逻辑Cleaner 虚引用异步回收滞后高并发下堆外内存堆积设置MaxDirectMemorySize上限后缓冲区总量超出限制。可直接运行复现代码javaimport java.nio.ByteBuffer;// 直接内存OOM复现public class DirectMemoryOomDemo {public static void main(String[] args) {while (true) {// 持续分配1MB堆外缓冲区ByteBuffer.allocateDirect(1024 * 1024);}}}配套 JVM 启动参数plain text-XX:MaxDirectMemorySize10m报错特征java.lang.OutOfMemoryError: Direct buffer memory生产核心坑点Netty、RPC、消息中间件高频出现该问题JVM 堆内存占用正常但服务器进程总内存持续上涨本质是堆外内存泄漏需手动通过 cleaner 回收缓冲区。OOM 错误java.lang.OutOfMemoryError: Direct buffer memory触发频繁分配 DirectByteBuffer没有及时释放Cleaner 回收滞后。复现循环调用ByteBuffer.allocateDirect()搭配-XX:MaxDirectMemorySize限制上限。JVM 运行时数据区遇到的问题Q1. java 虚拟机规范定义的运行时数据区有几块分别是什么A :一共有5块B分别是程序计数器java 堆java虚拟机栈本地方法栈方法区jdk8之后改成元空间。⚠️注意事项1. 直接内存不属于运行时数据区2. jdk7之前方法区实现为永久代PermGenjdk8之后彻底移除永久代使用元空间。3. 程序计时器是唯一没有OOM的内存区域。Q2.直接内存是什么又叫什么A: 直接内存又称为堆外内存。通过Unsafe向操作系统直接申请的内存典型 APIByteBuffer.allocateDirect()Netty 大量使用堆外缓冲区。存放 NIO 网络 IO 的数据缓冲区不在 Java 堆中不受-Xmx/-Xms限制。⚠️注意事项堆上只会生成一个很小的DirectByteBuffer代理对象真实数据存在堆外依靠Cleaner虚引用异步回收极易发生堆外内存泄漏调控参数-XX:MaxDirectMemorySizeOOM 报错Direct buffer memory。Q3JVM 的组成 运行时数据区 直接内存A这样说表述不严谨是不对的。JVM 完整的架构是五大子系统类加载子系统运行时数据区执行引擎gc子系统本地方法接口。B: 从进程内存占用视角看JVM 进程使用的内存包含运行时数据区 直接内存。直接内存不属于 JVM 规范定义的运行时数据区只是排查内存故障时需要一并分析。Q4如何系统掌握各区域 OOM 场景并且动手复现1熟记映射表异常日志 → 定位内存区域报错信息对应区域Java heap spaceJava 堆Metaspace元空间 (方法区)Direct buffer memory直接内存unable to create new native thread虚拟机栈StackOverflowError虚拟机栈2每一个 OOM Demo 强制配套 3 组 JVM 参数Plain Text# 1. GC日志输出-XX:PrintGCDetails -XX:PrintGCTimeStamps -Xloggc:gc.log# 2. OOM自动dump堆快照-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPathoom.hprof# 3. 限制对应区域内存稳定复现3分层练习① 基础 Demo 复现while 循环简易案例② 模拟业务真实内存泄漏ThreadLocal、静态缓存、动态类③ 使用 MAT 分析 hprof 快照定位引用链区分【内存泄漏 / 单纯内存不足】Q5OOM 发生之后JVM 进程一定会退出吗A不一定。如果代码捕获 OOM 异常当前线程终止其他线程可继续运行持续分配对象会反复抛出 OOM进程不会直接死掉。Q6StackOverflowError 和 OOM 的本质区别AStackOverflowError单个线程栈空间容量不足方法调用层级过深unable to create new native threadOOM系统资源不足无法新建线程分配栈内存。Q7发生堆 OOM一定是堆太小吗A不一定。两种场景业务流量变大对象总量上升 → 需要调大堆代码内存泄漏无用对象持续占用 → 改代码单纯调堆治标不治本。Q8为什么 Netty 优先使用直接内存A减少一次内核缓冲区与 JVM 堆缓冲区的数据拷贝减少一次内存复制零拷贝思想提升 IO 性能代价是堆外内存管理复杂容易泄漏。Q9程序计数器为什么不会产生 OOMA每条线程的程序计数器只保存一条指令地址内存占用极小JVM 规范明确规定此区域不需要回收不会出现内存溢出。
返回列表