JVM内存区域
首先,我们先回忆下, JVM结构中内存区域可以包括哪几个部分?
JVM(Java虚拟机)的内存结构主要分为以下几个部分,每个部分负责不同的功能,共同支撑Java程序的运行:
1. 程序计数器(Program Counter Register)
- 作用:
- 记录当前线程执行的字节码指令地址(行号)。
- 线程私有,确保多线程切换后能恢复到正确执行位置。
- 特点:
- 唯一不会抛出
OutOfMemoryError
的内存区域。
- 唯一不会抛出
2. Java虚拟机栈(Java Virtual Machine Stacks)
- 作用:
- 存储方法的栈帧(Frame),包括局部变量表、操作数栈、动态链接、方法出口等。
- 每个方法调用对应一个栈帧,方法结束时栈帧弹出。
- 线程私有:每个线程有独立的虚拟机栈。
- 异常:
StackOverflowError
:栈深度超过限制(如无限递归)。OutOfMemoryError
:栈扩展失败(如线程过多)。
3. 本地方法栈(Native Method Stack)
- 作用:
- 为JVM调用本地(Native)方法(如C/C++代码)提供服务。
- 与虚拟机栈的区别:
- 虚拟机栈执行Java方法,本地方法栈执行Native方法。
- 异常:同虚拟机栈(
StackOverflowError
/OutOfMemoryError
)。
4. Java堆(Java Heap)
- 作用:
- 存储所有对象实例和数组(通过
new
创建的对象)。 - 垃圾回收(GC)的主要区域。
- 存储所有对象实例和数组(通过
- 线程共享:所有线程共享堆内存。
- 分代设计(JDK8及之前):
- 新生代(Young Generation):
- Eden区、Survivor区(From/To)。
- 存放新创建的对象,Minor GC回收。
- 老年代(Old Generation):
- 存放长期存活的对象,Major GC/Full GC回收。
- 元空间(Metaspace,JDK8+):
- 替代永久代(PermGen),存储类元数据、方法区信息。
- 新生代(Young Generation):
- 异常:
OutOfMemoryError
(堆空间不足)。
5. 方法区(Method Area)
- 作用:
- 存储类信息(类名、字段、方法、常量池等)、静态变量(
static
)、即时编译器编译后的代码。
- 存储类信息(类名、字段、方法、常量池等)、静态变量(
- 实现变化:
- JDK7及之前:永久代(PermGen)实现。
- JDK8+:元空间(Metaspace)替代,使用本地内存(Native Memory)。
- 异常:
OutOfMemoryError
(如加载过多类)。
理清JVM和Native的栈和堆
有没有注意到,上面的栈包括2种:一种是JVM栈,一种是本地(Native)方法栈。
本地方法为什么需要独立的栈?
- 语言差异:Native方法通常由C/C++实现,其调用约定(Calling Convention)、栈帧结构与Java方法不同。
- 线程安全:每个线程需要独立的Native栈,避免多线程调用Native方法时上下文混乱。
因此,本地方法栈虽然使用操作系统的Native内存,但由JVM统一规划和管理(如线程创建时分配栈大小,线程结束时释放)。
也就是说程序运行过程中,调用JNI方法会切换到本地方法栈,从native调用java方法又会切换到Jvm栈。
本地方法栈可以认为是JVM管理的Native内存,调用JNI方法切换到本地方法内分配的堆这部分内存又是谁管理的?
直接分配的Native堆(如malloc/new分配的内存),开发者手动管理(需显式free/delete),是开发者或第三方库通过系统调用分配的完全独立的Native内存。因此JVM不参与管理,需手动防止泄漏。
直接内存(堆外内存)
对于java而言,除了上面5个内存区域,其实还有一个概念,那就是直接内存。什么是直接内存?简单说就是通过java的方法分配的Native的堆内存(Java堆外直接内存)。由操作系统的本地内存(Native Memory)管理的。它受 -XX:MaxDirectMemorySize
参数限制,可以看作 JVM 允许 Java 代码使用的受控本地内存.
直接内存的优缺点
优点
- 减少拷贝:避免JVM堆与Native堆之间的数据复制(如
SocketChannel
发送数据时无需复制到临时缓冲区)。 - 提升吞吐量:适合大规模、高频I/O操作(如Netty等网络框架默认使用Direct Buffer)。
- 绕过GC压力:大块内存分配在堆外,减少Full GC触发频率。
缺点
- 分配成本高:Direct Buffer的创建和销毁比Heap Buffer慢(涉及系统调用)。
- 内存泄漏风险:若被引用导致无法触发gc,或忘记释放(或
Cleaner
未触发),会导致Native堆内存泄漏。所以建议手动调用clean方法来释放。 - 调试困难:堆外内存问题通过常规比较难以直接观测。