Java虚拟机栈也是线程私有的,它的生命周期与线程相同,虚拟机栈描述的是Java方法执行的内存模型;每个方法在执行的同时都会创建一个栈帧(stack frame) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。
我们结合一个案例来看一下:
public class TestStack { public static void main(String[] args) { new PlayRice().print(); } } class PlayRice{ public void fun(){ System.out.println("干饭人,干饭魂,干饭都是人上人!!!"); } public void print(){ fun(); } }经常有人把Java 内存区域笼统的划分成堆内存(Heap)和栈内存(Stack),这种划分方式是直接继承自传统的 C、C++程序的内部结构,但是在Java语言里面显然是不合适的,Java的内存区域过分要比这两个更复杂,不过这种划分方式的流行也简洁说明了程序员最关注的、对象内存分配关系最密切的区域是 堆和栈,栈通常是指虚拟机,或者更多情况下只是指 虚拟机栈中的局部变量表的部分
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用
在《Java虚拟机规范中》,对这个区域规定了两种异常状况:
1. 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError
2. 如果Java虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常
本地方法栈(Native Method Stack)和虚拟机栈所发挥的作用是非常相似的,他们之间的区别就是虚拟机栈为虚拟机执行的Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native方法服务。
在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它,甚至有的Java虚拟机(Hot-Spot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机一样,本地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常。
1.4 堆Java堆是虚拟机所管理中内存最大的一块。Java堆是被所有线程共享的一个内存区域,在虚拟机启动时创建。这个内存区域的唯一目的就是存放对象的实例,Java世界里 几乎 所有的对象实例都在这里分配。
在《Java虚拟机规范》中对Java堆的描述是:“所有的对象实例以及数组都应当在堆上分配”。Java对是垃圾收集器管理的内存区域。从回收内存的角度看,现代的垃圾收集器大部分都是分代收集理论设计的,所以Java堆中经常会出现 “新生代、老年代、永久代、Eden、Survivor”。
根据《Java虚拟机规范》的规定,Java堆可以处在物理上不连续的内存空间中,但在逻辑上它应该被视为连续的,这点就像我们用磁盘空间去存储文件一样,并不要求每个文件都连续存放。但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简答、存储高效的考虑,很可能会要求连续的内存空间。
Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机会抛出OutOfMemoryError异常。
1.5 方法区方法区(Method Area)和Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 “非堆”(Non-Heap),目的是与Java堆区分开来。
《Java虚拟机规范》对方法区的约束是非常高宽松的,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,甚至还可以选择不实现垃圾收集,所以垃圾收集的行为在这个区域就会比较少出现。这个区域的内存回收目标主要是针对常量池的回收和类型的卸载,但是这个区域的回收效果就比较差强人意了。
如果方法区无法满足新的内存分配需求的时候,就会抛出 OutOfMemoryError异常。
1.6 运行时常量池运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。