概述
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途,以及创建时间和销毁时间。
----《深入理解Java虚拟机》
根据《Java虚拟机规范(JavaSE 7版)》的规定,Java虚拟机运行时数据区如下图所示
线程隔离区域
程序计数器
程序计数器是是一块较小的内存空间,可以看成是当前线程所执行的字节码的行号指示器。
Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。
- 若执行的是Java方法,记录的是正在执行的字节码指令地址
- Native方法,计数器值为空(Undefined)
此内存区是唯一一个Java虚拟机规范中没有规定任何OOM情况的区域
Java虚拟机栈
Java虚拟机栈也是线程私有的区域,它的生命周期与线程相同
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(运行时栈帧结构) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
异常状况:
- 线程请求的栈深度大于虚拟机所允许栈深度:StackOverflowError
- 扩展时无法申请到足够的内存,则会OOM
本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
可能发生的异常状况与虚拟机栈一致
线程共享区域
Java堆
对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时建。此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存。
Java堆是垃圾收集器(垃圾收集器与内存分配策略)管理的内存区域(GC堆),根据《Java虚拟机规范》的规定,Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
Java堆的大小可通过-Xmx
和-Xms
设定,此内存区域存在OOM异常。
方法区
方法区(非堆)用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。可以选择不实现垃圾收集,方法区无法满足新的内存分配需求时,将抛出OOM异常。
在JDK 8以前,许多Java程序员都习惯在HotSpot虚拟机上开发、部署程序,很多人都更愿意把方法区称呼为“永久代”(PermanentGeneration)
运行时常量池
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
一般来说,除了保存Class文件中描述的符号引用外,还会把由符号引用翻译出来的直接引用也存储在运行时常量池中。
它的重要特征是具备动态性,运行期间也可以将新的常量放入池中(String.intern()
)
直接内存
在JDK 1.4中新加入了NIO可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机硬件的限制,若各个内存区域总和大于物理内存限制,从而导致动态扩展时出现OMM异常。