Java虚拟机运行时的内存主要分为方法区、堆区、栈区、本地方法栈、程序计数器五个部分,如下图所示
(图片来源网络)
程序计数器
位于处理器内部,所包含的寄存器数量有限,由编译器按需分配。可以把程序计数器看成当前线程所执行的字节码的行号指示器,通过改变程序计数器的值来选择下一条需要执行的字节码指令,比如分支、循环、跳转、异常处理、线程恢复等都需要依赖程序计数器完成。
虚拟机栈
“后入先出”的存储结构,访问速度仅次于寄存器,比堆快,且栈数据可以被共享。Java虚拟机规范中定义了StackoverFlowError和OutofMemoryError两种与栈空间有关的异常,当请求的栈深度大于最大可用深度时抛出StackoverFlowError异常,扩展栈的过程中没有足够的内存空间时会抛出OutofMemoryError异常。
虚拟机栈是线程私有的,每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈,虚拟机栈表示Java方法执行的内存模型,每调用一个方法就会为每个方法生成一个栈帧(Stack Frame),用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用和完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。虚拟机栈的生命周期和线程是相同的。
本地方法栈
用于管理本地方法的调用,本地方法不是由Java实现,而是由C语言实现。任何本地方法接口都会使用某种本地方法栈,当县城调用Java方法时,虚拟机会创建一个新的栈帧并压入Java栈。然而当它调用的是本地方法时,虚拟机会把保持Java栈不变,不再在加栈中压入栈帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。
(图片取自:https://www.cnblogs.com/wade-luffy/p/5813747.html)
堆区
一种通用性的内存池,用于存放所有的Java对象。堆是一个运行时数据区,由垃圾回收来负责,是GC的重点回收区域,可动态分配内存大小,生存周期也不需要事先告诉编译器,大多数虚拟机中,对象和数组都存放在堆中。
方法区
主要保存的信息是类的元数据,比如类的类型信息、常量池、域信息、方法信息。
方法区是线程共享的,当两个线程同时需要加载一个类型时,只有一个类会请求ClassLoader加载,另一个线程则会等待。
参考资料:
《深入理解JVM & G1 GC》(周明耀/著)