LHJ's Blog

p0

Java内存区域

Java虚拟机所管理的内存空间被分割为若干个不同的数据区域,分别是:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区、运行时常量池。其划分的内存空间图如下所示:

p1

程序计数器

程序计数器(Program Counter Register)是一个较小的内存空间,它是当前线程所执行字节码的指示器,分支、循环、跳转、异常处理、线程恢复等都需要依赖程序计数器完成。

Java虚拟机的多线程是通过时间分片来实现的,在任何一个时刻,一个处理器只能执行一条命令,因此,为了线程切换后可以回到原来的正确位置,每个线程都需要一个程序计数器,各个线程之间的程序计数器互不影响。即程序计数器是线程私有的。它的生命周期和线程相同。

如果线程执行的是一个Java方法,那么程序计数器记录的是正在执行的Java虚拟机字节码指令地址,如果正在执行的是一个本地(Native)方法, 那么程序计数器记录的是Undefined。

程序计数器是唯一一个不会出现OOM(Out Of Memory Error)异常的区域。

Java虚拟机栈

和程序计数器相同,Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,其生命周期和线程相同。Java虚拟机栈描述的是Java方法执行的线程模型:每个方法执行的时候,Java虚拟机都会创建一个栈帧(存储局部变量表、操作数栈、动态链接、方法出口等信息),每一个方法被调用和调用结束对应着一个栈帧入栈和出栈的过程。

局部变量表中存储着已知的基本数据类型(byte、short、int、long、float、double、char、boolean)、对象引用和returnAddress类型(指向一条字节码指令的地址)。这些数据类型在局部变量表中以槽点(Slot)的形式表现,除了long和double类型外需要占用2个槽点,其它的数据类型只会占用1个槽点。局部变量表需要的内存空间在编译时就已经确定,方法运行时不会改变局部变量表的大小(槽点的数量,具体占用多少内存由虚拟机决定)

如果线程请求的栈深度超过了栈允许的最大深度,就会出现OutOfStackError异常,一般而言虚拟机栈都是可以扩展的,如果扩展的时候动态申请空间失败了,就会出现OOM(OutOfMemoryError)异常

本地方法栈

本地方法栈(Native Method Stack)和Java虚拟机栈相似,区别在于Java虚拟机栈为Java方法服务,而本地方法栈为本地(Native)方法服务

堆(Heap)是虚拟机管理的最大的一块内存区域。堆是由所有线程共享的。此内存的唯一目的就是存放对象实例。几乎所有的对象都在这里分配内存。

Java堆可以是固定大小的,也可以是可扩展的。如果一个对象在分配空间的时候没有足够的内存,而且Java堆也无法继续扩展的时候,就会出现OOM(OutOfMemoryError)异常。

方法区

方法区(Method Area)和堆一样也是线程共享的。它用于存储虚拟机加载的类信息、常量、静态变量等数据。在JDK8之前的HotSpot使用永久代(Permanent Generation)来实现方法区会出现一个问题:永久代有一个上限,这样导致了出现OOM(OutOfMemoryError)异常的问题。因此在JDK8之后,使用元空间(Meta Space)替代了永久代,这样的好处就是元空间的大小直接和硬件的内存大小挂钩。

同样的,如果在分配空间的时候失败了,也会出现OOM(OutOfMemoryError)异常。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分,Class文件中除了类的版本、字段、方法等信息外,还有一个常量池表(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类被加载进入Java虚拟机的时候被放置到方法区的运行时常量池中。

既然是属于方法区的一部分,也会出现OOM(OutOfMemoryError)异常。


 评论