内存区域划分
程序计数器(Program Counter Register)
- 记录当前线程执行的字节码指令地址(分支、循环、跳转等逻辑控制)
- 线程私有,生命周期与线程绑定
- 唯一不会发生内存溢出的区域
Java虚拟机栈(Java Virtual Machine Stacks)
-
存储方法调用的栈帧(Stack Frame),每个方法调用对应一个栈帧,包含局部变量表、操作数栈、动态链接等
- 动态链接:符号引用com.example.MyClass#myMethod转为直接引用0x7f3e8c,虚方法调用和接口方法调用时触发
- 虚方法通过extends实现,接口方法通过implements实现
- 虚方法表(vtable):子类虚方法完全复制父类的vtable,再追加自己的新方法;重写父类方法时,覆盖父类方法在vtable中位置
- 接口方法表(itable):支持多接口,动态链接
-
线程私有,每个线程独立分配栈内存
-
通过
-Xss
参数设置栈大小(例如-Xss1m
) -
内存溢出场景:
- StackOverflowError:栈深度超过限制(如无限递归调用)。(-Xss 虚拟机栈大小,通常为1MB)
- OutOfMemoryError:线程过多导致栈内存耗尽(常见于大量线程创建)(-Xsm)
本地方法栈(Native Method Stack)
- 为JVM调用本地(Native)方法(如C/C++代码)服务
- 线程私有,与虚拟机栈类似
- HotSpot将虚拟机栈与本地方法栈合并实现
- 溢出异常:与虚拟机栈相同(StackOverflowError、OutOfMemoryError)
Java堆(Java Heap)
-
存放对象实例和数组,是垃圾回收(GC)的主要区域
-
线程共享,几乎所有对象在此分配
-
通过
-Xms
(初始堆大小)、-Xmx
(最大堆大小)参数控制 -
进一步划分为新生代(Eden、Survivor区)和老年代
-
内存溢出场景(OutOfMemoryError: Java heap space):对象数量超过堆容量且无法被GC回收(如内存泄漏)
typescript// 堆溢出示例(无限创建对象) public class HeapOOMDemo { public static void main(String[] args) { List<Object> list = new ArrayList<>(); while (true) { list.add(new Object()); // 无限添加对象导致堆溢出 } } }
方法区(Method Area)
-
存储类信息、常量、静态变量、即时编译器编译后的代码等,线程共享
-
JDK 8之前:称为"永久代(PermGen)",通过
-XX:PermSize
、-XX:MaxPermSize
配置 -
JDK 8及之后:改为"元空间(Metaspace)",使用本地内存,通过
-XX:MetaspaceSize
、-XX:MaxMetaspaceSize
配置 -
内存溢出场景:
- OutOfMemoryError: PermGen space(JDK 8前):加载过多类(如动态生成类、反射滥用)
- OutOfMemoryError: Metaspace(JDK 8后):元空间内存不足
csharp// 方法区溢出示例(动态生成类) public class MetaspaceOOMDemo { static class OOMObject {} public static void main(String[] args) { int i = 0; try { while (true) { // 使用CGLIB动态生成类 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1)); enhancer.create(); // 无限生成代理类导致元空间溢出 i++; } } catch (Throwable e) { System.out.println("动态生成类次数: " + i); throw e; } } }
运行时常量池(Runtime Constant Pool)
-
存储类文件中的常量池表(如字面量、符号引用)
- 属于方法区的一部分(JDK 8后属于元空间)
-
内存溢出场景:与方法区溢出类似(如大量字符串常量)
直接内存(Direct Memory)
-
通过
DirectByteBuffer
或NIO的allocateDirect
分配的堆外内存,避免Java堆与Native堆间数据复制 -
不受JVM堆大小限制,但受物理内存限制
-
通过
-XX:MaxDirectMemorySize
设置最大直接内存 -
内存溢出场景(OutOfMemoryError: Direct buffer memory):频繁分配堆外内存未释放
arduino// 直接内存溢出示例 public class DirectMemoryOOMDemo { public static void main(String[] args) { List<ByteBuffer> list = new ArrayList<>(); while (true) { // 分配直接内存(默认不受-Xmx限制) ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 100); // 100MB list.add(buffer); } } }
对象创建与访问
对象创建过程
-
类加载检查:检查类是否已被加载、解析和初始化;若未加载,则执行类加载过程(加载、验证、准备、解析、初始化)
-
内存分配:利用指针碰撞(适用于内存规整)或者空闲列表(适用于内存不规整)进行内存分配
- 并发问题:利用TLAB进行为每个线程预先分配一小块内存,TLAB不足时使用CAS机制分配内存
-
初始化0值:int为0,boolean为false
-
设置对象头:Mark Word(哈希码、GC分代年龄、锁状态) + Klass Pointer(元数据) + 数组长度(仅限数组)
- 元数据:JVM 在运行时用于描述类信息的数据结构,它存储了类的类型、方法、字段、继承关系、注解等详细信息
对象的内存布局
- 对象头:Mark Word(8B) + Klass Pointer(4B/8B) + 数组长度(4B)
- 实例数据:对象的实例字段,父类字段在前,子类字段在后
- 对齐填充:确保对象的大小是8字节的整数倍(内存对齐),提高CPU访存效率
对象的访问定位
- 句柄访问:在堆中划分一块句柄池,存储对象的实例数据指针和类型数据指针,栈中的引用指向句柄池中的句柄
- 直接指针访问:栈中的引用直接指向堆中的对象实例数据,对象头中的Klass Pointer指向方法区中的类元数据
- Hotspot的实现:直接指针访问(性能优先)
对象创建与访问的优化技术
-
逃逸分析(Escape Analysis):分析对象的作用域是否仅限于方法内部(不逃逸、方法逃逸、线程逃逸)
- 栈上分配:若对象未逃逸,直接在栈上分配(减少GC压力)
- 标量替换:将对象拆分为基本类型字段,分配在栈上
-
锁消除 (Lock Elision):若对象未逃逸且未共享,消除不必要的同步锁
-
TLAB(Thread Local Allocation Buffer):为每个线程预先分配一小块内存,避免多线程竞争
内存溢出实战
堆内存溢出(Java Heap Space)
- 内存泄漏:对象被无意长期引用(如静态集合类缓存数据未清理)
- 大对象分配:一次性加载超大文件或数据集到内存(如大数组、缓存数据)
- 配置不足:堆内存(
-Xmx
)设置过小,无法支撑应用正常运行 - 诊断步骤:生成堆转储文件
jmap -dump:format=b,file=heapdump.hprof <pid>
- 解决方案:代码修复、参数调优、监控告警
栈溢出(StackOverflowError)
- 无限递归:递归调用未设置终止条件
- 线程过多:高并发场景下创建大量线程(每个线程占用独立栈空间)
- 诊断步骤:查看线程栈
jstack <pid> > thread_dump.txt
- 解决方案:修复代码逻辑、调整栈大小、控制线程数量
方法区/元空间溢出(Metaspace)
- 动态生成类:频繁使用反射(
Class.forName()
)、CGLIB或ASM生成代理类 - 大量加载类:应用依赖过多第三方库或未关闭的类加载器
- 诊断步骤:监控元空间的使用
jstat -gcmetacapacity <pid> 1000
- 解决方案:增大元空间、启动类缓存(CGLIB的
setUseCache(true)
)、减少动态类的生成
直接内存溢出(Direct Buffer Memory)
- NIO操作:频繁分配堆外内存(如
ByteBuffer.allocateDirect()
)未释放 - JNI调用:本地代码(如C/C++)未正确释放内存
- 诊断步骤:监控直接内存
jcmd <pid> VM.native_memory
- 解决方案:限制直接内存大小、显式触发GC(
System.gc()
)、手动释放内存(buffer.cleaner().clean()
)
通用诊断工具与流程
工具 | 用途 |
---|---|
jmap |
生成堆转储文件(Heap Dump) |
jstack |
获取线程栈快照,分析死锁或栈溢出 |
jstat |
监控GC、类加载、编译统计信息 |
VisualVM | 图形化监控内存、线程、CPU使用率 |
MAT | 分析堆转储,定位内存泄漏根源 |
Arthas | 在线诊断工具,支持动态查看类加载、方法调用 |