----------------JAVA虚拟机所管理的内存包括如下几个运行时数据区域:
1、程序计数器:是一块较小的数据空间,可以看作当前线程所执行的字节码的行号指示器(?),字节码解释器工作时就是通过改变这个程序计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程的计数器之间无任何影响,独立存储,我们称这类内存区域为‘线程私有’的内存。
如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值为空(Undefined)。此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemroyError情况的区域。
2、JAVA虚拟机栈:与程序计数器一样,java虚拟机栈也是线程私有的,它的生命周期与线程相同,虚拟机栈描述的是java方法执行的内存模型;每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息(?),每个方法在调用直至执行完成的过程,就对应这一个栈帧在虚拟机栈中入栈到处栈的过程。
局部变量表:存放了编译期可知的各种基本数据类型(boolean,double,float,long,int,short,char,byte)、对象引用(reference类型,不等同于对象本身,可能是指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)(?)。
long和double(均64位)占用2个局部变量空间(slot),其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间(?)完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间完全确定的,在方法运行期间不会改变局部变量表的大小。
异常状况:若线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(大部分java虚拟机都可以扩展),若扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
3、本地方法栈:与虚拟机栈所发挥的作用是非常相似的,区别只不过是虚拟机栈为虚拟机执行java方法(也就是字节码)服务,本地方法栈为虚拟机所使用的本地方法服务(Native方法)(?)
4、java堆:(java heap)是java虚拟机所管理的内存中最大的一块,java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
java堆是垃圾收集器管理的主要区域,因此很多时候也被称为GC堆。
从内存分配的角度,java堆中可能划分出多个线程私有的分配缓冲区(TLAB),不过无论如何划分,都与存放内容无关,无论哪个区域,存放的都仍然是对象实例,进一步划分的目的是为了更好的回收内存,或更快的分配内存。
java堆可以处于物理上不连续的内存空间中,只要逻辑上连续即可。
异常:如果在堆中没有内存完成实例分配,而且堆无法再扩展时,将会抛出OutOfMemoryError异常。
5、方法区:与java堆一样,是各个线程共享的内存区域,用与存储已被虚拟机加载的类信息、常量、静态常量、即使编译器编译后的代码等数据。(?)
不需要连续的内存并且可以选择固定大小或者可扩展,还可以选择不实现垃圾收集。这区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
6、运行时常量池:是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用(?),这部分内容在类加载后进入方法区的运行时常量区存放。
运行时常量区是方法区的一部分,自然收到方法区内存的限制,当常量池无法在申请到内存时会抛出OutOfMemoryError异常。