JVM 把内存 划分为不同的区域,各区域有不同的用途、创建时间、销毁时间, 有些区域随着JVM进程的启动而存在,有些区域则依赖用户的线程而启动和销毁。
程序计数器: 一块较小的内存空间。 在虚拟机的概念模型里(仅存在概念模型中,各种虚拟机可能会通过其他更加高效的方式来实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令, 分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,
在任何一个确定的时刻,一个处理器(多核处理器中的一个核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各条线程之间计数器互相不影响,独立存储,我们对这内存区域称之为“线程私有”的内存。
如果线程正在执行的是一个java的方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址; 如果正在执行的是Native放方法,这个计数器的值为空(Undefined)。
此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。
Java虚拟机栈 : 这个也是线程私有的, 生命周期与线程相同。这个对java方法内存模型的描述:
每个方法在执行的时候,都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、操作数出口等。每个方法从调用到执行完成的过程,就对应一个栈帧在虚拟机中入栈到出栈的过程。
局部变量表存放了编译期可知的各种基本数据类型(boolean\byte\char\short\int\float\long\double)、对象引用(refrence类型,不等同于对象本身,可能是一个纸箱对象起始地址的引用指针,也可能是一个指向对象的句柄或其他与此对象相关的位置)、returnAddress类型(指向一条字节码指令的地址)
64位长度的long 和double 占两个局部变量空间,其余都是一个。 局部变量表在编译期就分配好,之后就不会再变化。
在该区域有两种异常情况: 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出
StackOverflowError异常; 如果虚拟机栈可以动态扩展(当前大部分java虚拟机都可以动态扩展,只不过java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就抛出
OutOfMemoryError 异常。
本地方法栈: 与虚拟机栈发挥的作用很相似。区别在于虚拟机栈为虚拟机执行的Java方法(字节码)服务,而本地方法栈则为虚拟机用到的Native服务。虚拟机规范没有规定本地方法栈所使用的语言、方式、数据结构,可自由实现。还可以和虚拟机栈合起来(sun HostSpot 虚拟机)。抛出的异常有:
StactOverflowError 、OutOfMemoryError
Java推: JVM中管理的内存中最大的一块。
此内存区域的唯一目的就是存放对象实例(包过数组),几乎所有的对象实例都在这里分配内存。随着JIT 编译器的发展与逃逸分析技术逐渐成熟,会发生变化,所有是几乎,而不是所有对象。
Java推是垃圾收集器管理的主要区域。从内存回收的角度看,由于现在收集器基本采用 分代收集算法,所有Java推可细分: 新生代和老年代,再细分一点就有:Eden空间、From Survivor 空间、To Survivor 空间。从内存分配角度看: 线程共享的Java推中可能划分出多个线程私有的分配缓冲区。
Java推 可固定大小, 也可扩展,当前主流虚拟机都按照可扩展实现(通过-Xmx 和-Xms 控制)
方法区:
用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
Sun HotSpot 虚拟机 将方法区称为永久代,但是本质上并不等价。 其他虚拟机(BEA JRockit \IBM J9等)不存在永久代
运行时常量池: 是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
直接内存: 不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。(JDK1.4 新加入了NIO(New Input/Output )类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配到推外内存,然后通过一个存储在Java推中的DirectByteBugger对象作为这块内存的引用直接操作)
------------------------------------------------
《深入理解Java虚拟机》