Java虚拟机在Java执行的过程中会将它所管理的内存区域划分为若干个不同的数据区域。Java7.0划分如下
其中:
线程共享:方法区、堆
线程私有:虚拟机栈、本地方法栈、程序计数器
注:在Java8中移除了永久代(方法区),通过元数据区(meataspace)存储数据。元数据区本质与永久代类似,都是对方法区的实现。
区别:元数据空间不在虚拟机中,而是使用本地内存
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是记录当前线程所执行的字节码的行号指示器。
作用:为了保证程序能够连续地执行下去,CPU必须具有某些手段来确定下一条指令的地址。而程序计数器正是起到这种作用,因此又被称为指令计数器。
特点:
虚拟机栈描述的是Java方法执行的内存模型
。
每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储 局部变量表、操作数栈、动态链接、方法出口 等信息。
每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
特点:
异常
StackOverflowError
:线程请求的栈深度大于虚拟机允许的深度,则抛出此异常OutOfMemoryError
:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。栈帧(Stack Frame)
:用于支持虚拟机进行方法调用和方法执行的数据结构。
作用:
存储方法的局部变量表、操作数栈、动态链接、方法出口等信息。
在编译代码的时候,栈帧中需要多大局部变量表内存,多深操作数栈都已经完全确定。不会受到程序运行期变量数据的影响。
注意:
在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作
Java虚拟机栈中占用内存最多的是:局部变量表
。
是一组变量值存储空间,用于存放 方法参数
和方法内部定义的局部变量
。
局部变量表中存放了编译期可知的各种基本数据类型
(boolean、byte、char、short、int、float、long、double)、对象引用
(reference类型)和 returnAddress
类型(指向了一条字节码指令的地址)
其中64位长度的 long 和 double 类型占用两个局部变量空间(Slot),其余数据类型只占用1个。
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法的运行期间不会改变局部变量表的大小。
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用相似,区别在于虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机执行Native方法服务。
异常:StackOverflowError 和 OutOfMemoryError
特点:线程私有
Java堆(Java Heap)是Java虚拟机所管理内存中最大的一块。是被所有线程共享的一块内存区域,在虚拟机启动时创建。线程共享,主要存放对象实例和数组。
下面是Java堆内存划分图(图片来源:https://blog.51cto.com/lizhenliang/2164876)
注:
Java8使用元空间(MetaSpace)代替永久代,它们都是方法区的实现,最大的区别是:元空间并不在JVM中,而是使用本地内存。
Java堆是垃圾收集器管理的主要区域,因此也被称做“GC堆”(Garbage Collected Heap)。
Java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可。
异常:OutOfMemoryError
特点:
方法区(Method Area)与Java堆一样,是线程共享的内存区域,它用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码 等数据。它的别名 Non-Heap
(非堆)。在HotSpot虚拟机中方法区也被称为 永久代
作用:
主要存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
异常:OutOfMemoryError
特点:
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。