JVM之运行时数据区域

运行时数据区主要分为七部分,程序计数器(Program Counter Register)、虚拟机栈(VM Stacks)、本地方方法栈(Native Method Stacks)、堆(Heap)、方法区(Area Method)。 [read more=”Read more” less=”Read less”] 程序计数器(Program Counter Register) 一小块内存,是当前线程所执行字节码的行号指示器,字节码解释器在工作的时候就是通过改变这个计数器的值来选取下一条需要执行的指令,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖次计数器来完成。多线程是通过线程切换并且分配处理器执行时间来实现的,在任何一个确定的时间,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各条线程计数互不影响,独立存储,这块内存为线程私有内存。如果是Java方法,计数器记录的是正在执行的虚拟机字节码指令地址;如果是Native方法(非Java代码接口的方法),计数器的值为空。此区域是JVM唯一一个没规定OutOfMemoryError情况的区域。
虚拟机栈(VM Stacks) 线程私有和PCR一样,生命周期和线程相同,描述的是Java方法执行的内存模型,每个方法执行都会创建一个栈帧,来存储局部变量表、操作栈数、动态连接、方法出口等信息,每个方法从执行到完成对应的虚拟机栈的入栈到出栈的全过程。有人把JVM内存区分为堆内存和栈内存,所指的栈就是虚拟机栈,具体是指虚拟机栈的局部变量表部分。此部分存放了编译期的基本数据类型和对象引用类型,其中double和long占2个局部变量空间,其余类型占1个。在编译期间完成局部变量表的内存分配,当进入方法时,分配多大的空间是完全确定的,运行期间不会再改变。此区域规定两种异常,1、当线程请求栈深度大于虚拟机栈所允许的深度,抛出StackOverFlowError。2、动态扩展时无法申请到足够的内存,抛出OutOfMemoryError。
本地方法栈(Native Method Stacks) 和虚拟机栈发挥的作用相似,主要是服务的对象不同,是为Native Method服务 。此区域规定两种异常,1、当线程请求栈深度大于本地方法栈所允许的深度,抛出StackOverFlowError。2、动态扩展时无法申请到足够的内存,抛出OutOfMemoryError。
堆(Java Heap)JVM中管理的最大的一块内存,所有线程共享,在虚拟机启动时创建。唯一目的存放对象实例,几乎所有对象都存放在此。所以是GC收集的主要区域,现在收集主要是采用分代收集算法,分为年轻代和年老代,在细分为Eden空间、From Survivor空间、To Survivor空间,在共享的内存中,可能会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer TLAB),目的是更好的回收内存,更快的分配内存。此内存在物理机上可以是不连续的,只要逻辑上连续即可,通过-Xmx -Xms控制扩展大小。此区域异常,1、堆中没有内存来完成实例分配,堆也无法再扩展,抛出OutOfMemoryError。
方法区(Method Area)线程共享 存放加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。属于堆的一个逻辑部分,为了区分起了别名Non-Heap。在HotSpot虚拟机上把此区域称为“永久代”(Permanent Generation),本质上两者不等价,仅仅是HotSpot团队把GC分代收集扩展到方法区,或者说用永久代来实现方法区而已,这样就像管理Java Heap一样管理这部分内存。对于其他虚拟机(BEA JRockit/IBM J9等)不存在永久代的概念。现在看来不是一个好主意,因为这样更容易遇到内存溢出的问题(-XX:MaxPermSize有上限),在JDK1.7中已经把原本存放永久代的字符串常量全部移出。也有放弃永久代逐步改为Native Memory来实现方法区域。此区域异常,1、当方法区无法满足内存分配的时候,抛出OutOfMemoryError。运行时常量池方法区的一部分,存放编译器生成的各种字面量和符号引用。运行期也可能将新的常量放入池中,String的intern()方法,直接使用双引号声明出来的String对象会直接存储在常量池中。如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。
直接内存(Direct Memory) 不属于JVM规范定义的内存部分。是JDK1.4新加了NIO类,引入了一种基于通道(Channel)与缓冲区(Buffer)的IO方式,可以使用Native函数直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这应避免Java堆和Native堆来回复制数据。分配内存时候需要考虑直接内存。[/read]

评论已关闭。