5-1JVM内存区域
一、核心内存区域划分
JVM内存区域主要分为以下五大模块,各区域分工明确,共同支撑Java程序的运行:
1. 程序计数器(Program Counter Register)
作用:记录当前线程执行字节码指令的地址(类似于“书签”),确保线程切换后能恢复执行位置。
特点:
线程私有,每个线程独立存储。
唯一不会发生内存溢出(OutOfMemoryError)的区域。
执行Java方法时记录指令地址,执行Native方法时值为空。
2. 虚拟机栈(JVM Stack)
作用:存储方法调用的栈帧(Frame),每个栈帧包含局部变量表、操作数栈、动态链接和方法出口信息。
特点:
线程私有,每个线程对应一个栈。
可能抛出异常:
StackOverflowError:递归调用过深或方法嵌套层级过多(如无限循环调用)。
OutOfMemoryError:线程数过多导致栈内存耗尽。
通过参数 -Xss 设置栈大小(默认1MB)。
3. 本地方法栈(Native Method Stack)
作用:支持Native方法(如C/C++实现的系统调用)的执行,结构与虚拟机栈类似。
特点:
线程私有,HotSpot虚拟机中与虚拟机栈合并实现。
4. 堆(Heap)
作用:存储所有对象实例及数组,是垃圾回收(GC)的主要区域。
结构:
新生代(Young Generation):包含Eden区(新对象分配区)和两个Survivor区(S0/S1,用于Minor GC后存活对象的暂存)。
老年代(Old Generation):存放长期存活的对象(如经过多次GC仍存活的对象)。
特点:
线程共享,通过 -Xms(初始堆大小)和 -Xmx(最大堆大小)配置。
常见问题:OutOfMemoryError(对象过多或内存泄漏)。
5. 方法区(Method Area)/元空间(Metaspace)
作用:存储类信息(如类名、方法代码)、常量、静态变量、运行时常量池等。
演变:
Java 8前:称为“永久代(PermGen)”,通过 -XX:PermSize 和 -XX:MaxPermSize 配置。
Java 8及以后:改为元空间(Metaspace),使用本地内存,通过 -XX:MetaspaceSize 配置。
可能抛出异常:OutOfMemoryError: Metaspace(类加载过多或动态代理滥用)
二、内存区域扩展说明
1. 直接内存(Direct Memory)
作用:NIO的DirectBuffer使用的非堆内存,通过 -XX:MaxDirectMemorySize 控制大小。
特点:可能因未释放导致内存溢出,需手动管理或依赖GC。
2. 线程共享与私有区域
共享区域:堆、方法区/元空间。
私有区域:程序计数器、虚拟机栈、本地方法栈。
三、内存分配与调优实践
对象分配流程:
TLAB(线程本地分配缓冲区):优先在Eden区分配,减少锁竞争。
晋升老年代条件:对象年龄阈值(默认15次Minor GC存活)或大对象直接分配。
调优建议:
堆大小设置:根据应用负载调整 -Xms 和 -Xmx,避免频繁Full GC。
栈深度控制:避免过深递归或过多线程。
元空间监控:动态类加载场景需关注Metaspace增长。
四、常见问题示例
内存溢出场景:
堆溢出:循环创建未释放的大对象(如 List.add(new Object()))。
栈溢出:无限递归调用方法。
线程安全问题:
虚拟机栈:局部变量是线程私有的,不存在共享问题;但若对象引用逃逸到堆中,可能引发线程安全问题。