JVM运行时数据区主要分为线程私有的程序计数器、虚拟机栈、本地方法栈和线程共享的堆、方法区(JDK8后为元空间),这些区域存储和管理Java程序运行时的各种数据。
程序计数器
程序计数器(Program Counter Register)是线程私有的数据区,用来记录当前线程正在执行的字节码指令地址。如果执行的是Java方法,则记录下一条指令地址;如果是native方法,则为空。
程序计数器不会发生OutOfMemoryError,是唯一没有此问题的区域。因为它是小内存,按线程独占,生命周期和线程相同。
字节码是由解释器逐条执行的,需要改变执行位置时,通过计数器指示下一条指令。
虚拟机栈
Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,其生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型,每个方法调用都会创建一个栈帧。
栈帧存储局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表用于存储方法内的局部变量,如基本数据类型和对象引用。
StackOverflowError表示栈深度请求大于虚拟机允许深度;OutOfMemoryError表示无法申请足够内存。
本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈类似,也是线程私有的,用于支持native方法执行。native方法是用非Java语言编写的。
不同虚拟机实现本地方法栈的方式可能不同,比如HotSpot虚拟机直接将它与虚拟机栈合一。
同样可能抛出StackOverflowError和OutOfMemoryError。
堆
堆(Heap)是JVM最大的一块内存,也是所有线程共享的,唯一存放对象实例的目的地。GC的主要管理区域,从内存分配角度分为新生代和老年代。
新生代包括Eden、From Survivor、To Survivor;老年代存放长期存活对象。新生代对象存活时间短,大部分死亡,早早收集减少系统内存压力。
如果堆没有内存完成实例分配,会抛出OutOfMemoryError。
方法区
方法区(Method Area)是线程共享的内存区域,用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
JDK7及之前是永久代(PermGen),容易出现OutOfMemoryError;JDK8后改为元空间(Metaspace),使用直接内存。
运行时常量池是方法区的一部分,存放Class文件中的常量池,如数字、字符串字面量。动态性:String.intern()可将运行时字符串放入池中。
直接内存
直接内存(Direct Memory)不是JVM运行时数据区的一部分,但受限于总内存和处理器寻址限制。NIO的ByteBuffer支持直接内存操作,避免在用户态和内核态间复制数据。
使用Unsafe类分配直接内存,可能抛出OutOfMemoryError,即使总内存没满,检查总内存时忽略了堆内存。
FAQ:
Q: JVM运行时数据区哪些是线程共享的?
A: 堆和方法区(元空间)是所有线程共享的。
Q: 程序计数器为什么不会OOM?
A: 因为它是线程私有的小内存,按线程独立计算。
Q: 堆和栈有什么区别?
A: 堆存对象实例,线程共享;栈存栈帧,包括局部变量,线程私有。
Q: 永久代为什么被移除?
A: 永久代容易OOM,且垃圾回收复杂,元空间用本地内存更灵活。