概述
在java虚拟机中,其将内存区域划分为:程序计数器、java虚拟机栈、本地方法栈、方法区、堆。这些不同的数据区域存放者不同的数据类型,也拥有不同的作用。其中方法区和堆是线程共享的,程序计数器、java虚拟机栈、本地方法栈是线程私有的。其关系大致如下图所示(图中区域大小并不表示内存中真正的大小)
程序计数器
程序计数器是一块比较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。也就是说,其用于标识程序下一个执行命令是哪条语句,比如 if 分支、while循环语句的结束和跳转都需要依靠程序计数器来完成,再比如函数A中调用函数B,在函数A调用时会依靠程序计数器中记录相关的值,当函数B调用结束时依靠程序计数器中读取原先的值获取下一条指令,从而保证函数B调用完后仍能继续在函数A中执行后续的语句。此内存区域是唯一 一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域,换句话说,此区域不会发生OutOfMemoryError
java虚拟机栈
java虚拟机栈描述的是java方法执行的内模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。换句话说,此区域存放着函数内的局部变量,当我们在函数中定义变量时,这些局部变量就存放在这个栈区域中。
这个区域可能会抛出两种异常:
- StackOverflowError异常:当调用栈深度超过虚拟机允许的栈深度时,抛出此异常。常见于递归调用中。
- OutOfMemoryError异常:如果虚拟机栈可以动态扩展但是在扩展时无法申请到足够内存时抛出此异常。
本地方法栈
本地方法栈与java虚拟机栈类似,只不过java虚拟机栈是非native方法存储的空间,而本地方法栈是native方法存储的空间。其同样会抛出StackOverflowError异常和OutOfMemoryError异常。
java堆
对绝大多数应用来说,堆是java虚拟机中内存最大的一部分,也是垃圾回收的主要区域。这个区域中的内容是共享的,也就是说,在某个函数中创建的对象存放在这个区域时,当函数结束离开,这个对象并不会跟着函数消失,仍然可以被其他函数使用。当我们使用new创建一个对象或数组时,这个新建出来的对象或数组就是存放在堆中。java虚拟机规范中指出,java堆不必处于物理上的连续空间,只需要逻辑上连续即可。
当新建一个对象时,在堆中没有足够的内存分配给对象时,将会抛出OutOfMemoryError异常。
方法区
方法区主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。也就是说,Class类的元数据信息(用于准确定义一个类,区分于其他类的信息,比如:类名、访问修饰符等)以及我们在程序中定义的常量和静态变量都存放在此区域中。这个区域也是线程共享的,不受限于具体的类或方法。
当方法区无法满足分配内存需求时,会会抛出OutOfMemoryError异常。
运行时常量池
运行时常量池(Runtime Constant Pool)是方法区中的一个区域,主要用于存放编译期生成的各种字面量和符合引用。我们在程序中定义的常量和Sting变量的值就存放在这个区域中。
虽然这个区域主要用于存放编译期生成的常量,但是这个区域仍然具有动态性。主要是因为java语言并不要求常量一定只有在编译期才能产生,在运行期间也能产生常量并将其放入运行时常量池中,其中最主要的方法便是String.intern()
方法。
参考资料
[1] 周志明,深入理解Java虚拟机:JVM高级特性与最佳实践[M],北京:机械工业出版社,2013