JVM内存区域无法用Java代码直接创建,但可通过触发各区域边界行为来验证:堆溢出用固定-Xmx配合持续new对象;元空间溢出用-XX:MaxMetaspaceSize限制并动态加载类;栈溢出用-Xss压窄栈或无限递归;直接内存溢出用-XX:MaxDirectMemorySize限制并allocateDirect申请。

直接用代码“模拟”JVM内存区域本身不可行——因为这些区域是JVM运行时由HotSpot等虚拟机内部管理的底层结构,不是Java层能new出来的对象。但你可以通过**精准触发各区域的边界行为**,让JVM自己暴露它们的存在、容量和异常特征。关键不在于“画个图”,而在于“让它报错”或“让指标动起来”。
堆内存溢出:最直观的验证方式
堆是唯一存放Java对象实例的地方,也是OOM最常发生的区域。写一段持续分配对象却不释放的代码,配合固定堆大小参数,就能稳定复现java.lang.OutOfMemoryError: Java heap space:
- 启动参数加:
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError(强制堆固定20MB,出错时自动生成dump) - 代码只需不断
new byte[1024*1024](每次分配1MB数组),几十次就撑爆 - 观察日志:报错后生成
java_pidxxx.hprof,可用JVisualVM或Eclipse MAT打开,确认全是byte[]实例堆积
元空间(Metaspace)膨胀:加载大量动态类
方法区在JDK 8+叫元空间,使用本地内存。它撑爆的典型场景是反复定义、加载新类(如某些RPC框架、热部署、字节码生成库):
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
- 启动参数:
-XX:MaxMetaspaceSize=32m(限制元空间最大32MB) - 用
ASM或ByteBuddy在循环里动态生成1000个不同名字的类,并用ClassLoader.defineClass加载 - 触发异常:
java.lang.OutOfMemoryError: Metaspace,说明类元数据已占满配额 - 对比
jstat -gc <pid></pid>输出中的MU(Metaspace Used)和MC(Metaspace Capacity)值,会看到它们一路飙升到上限
虚拟机栈溢出:递归或深度调用链
每个线程私有栈默认约1MB(Linux下可查ulimit -s),超深调用就会触发StackOverflowError:
- 写一个无终止条件的递归方法,比如
void foo() { foo(); } - 或者用
-Xss128k把栈大小压到128KB,再调用一个深度为500的递归(每层约256字节局部变量),很快崩 - 注意:这不是堆OOM,而是线程栈帧压栈失败,错误信息明确带
StackOverflowError,和堆无关
直接内存泄漏:绕过堆的本地内存申请
直接内存不在JVM运行时数据区规范内,但ByteBuffer.allocateDirect()会调用Unsafe.allocateMemory()向OS申请内存,受-XX:MaxDirectMemorySize限制:
- 启动参数:
-XX:MaxDirectMemorySize=10m - 循环执行
ByteBuffer.allocateDirect(2 * 1024 * 1024)(每次2MB),5次后必抛OutOfMemoryError: Direct buffer memory - 此时
jstat -gc <pid></pid>中堆相关指标(S0U、EU、OU)几乎不变,但进程RSS内存持续上涨——这就是直接内存在吃系统物理内存
不复杂但容易忽略:所有测试必须配合对应JVM参数才有效。光写代码不设限,JVM会自动扩容或GC回收,你永远看不到异常。真正的“模拟”,是人为制造资源瓶颈,逼JVM按区域职责报错——错在哪,哪就是它的地盘。

















