Java 内存区域分析

  Java虚拟机在执行的过程中会将物理内存划分为几个区域来进行管理,这些区域都有特定用途和生命周期。Java虚拟机规范中提到,有以下6个部分组成:

1:寄存器(PC Register)

  Java虚拟机的多线程是通过轮流切换分配处理器执行时间来实现的,在任何一个时间点上,一个处理器只能执行一个线程中的指令,其余线程中断状态。因此为了记录每个线程当前所执行的指令,每个线程都会拥有一个独立的PC寄存器,用于保存线程执行状态。而且寄存器互不影响,类似这样的内存空间也叫做“线程私有”内存。

  特别的,如果执行的方法是native方法,寄存器是不确定的(undefined),如果是Java方法,寄存器保存的是当前字节码指令地址。PC寄存器的容量至少应当能保存一个 returnAddress 类型的数据或者一个与平台相关的本地指针的值。所以在计算内存使用的时候,这一部分可以忽略不计。

2:虚拟机栈(Java Virtual Machine Stacks)

  虚拟机栈也是线程私有的,与线程生命周期一致,创建的时候分配,终止的时候回收。在内存中以帧的形式存储,用于保存线程的局部变量表,局部计算结果,一个方法开始执行到返回结果,对于栈来说就是一个帧入栈和出栈的过程。其中局部变量表中包含了基本数据类型和引用对象(对象地址指针,字节码地址等等)

  在Java虚拟机规范第一版中,Java虚拟机栈也被直接称作Java栈,这个规范允许要么以固定尺寸来分配空间或者能动态扩展。如果栈大小是固定的,创建栈的时候,每一个Java虚拟机栈大小都可以独立选定。即通过参数决定栈大小或者最大最小值,内存可不要求连续。Java虚拟机的栈可以分配于更底层语言中的堆中。

  Java虚拟机栈上操作会有两个异常需要考虑,一是线程计算请求的栈深度(容量)超过虚拟机所允许的深度,将抛出StackOverflowError异常,如果虚拟机可以动态扩展,并试图扩展,但内存不足了,或者没有足够内存创建新线程的时候,将抛出OutOfMemoryError异常。

3:堆(Heap)

  堆是可供所有线程共享的区域,类实例和数组分配的区域。启动虚拟机的时候创建,堆中存储的对象受自动内存管理(GC),所以无需手动释放。空间分配也可以是固定大小或动态扩展,内存可不要求连续,这点和栈是一样的。如果需求的堆容量超过了自动内存管理能提供的最大容量,也会抛出OutOfMemoryError异常。

4:方法区(Method Area)

  方法区同样是线程共享的区域,有点类似于传统语言的“Text Segment”区。存储了类结构,例如:运行时常量池,字段和方法数据,以及构造函数和普通方法的字节码内容。还包括一些类,实例,接口初始化时用到的特殊方法。特殊方法例如init等等。

  方法区同样在虚拟机启动的时候创建,尽管方法区是堆的逻辑组成中的一部分,不过这个区可以实现垃圾回收,也可以不实现,规范不做强制要求,代码编译管理策略和物理内存分配都不做要求,具体得看虚拟机的实现了。

  当然,如果申请不到足够的内存,也会抛OutOfMemoryError。

5:运行时常量池(Run-Time Constant Pool)

  顾名思义,是每一个类或接口常量表现形式,包括若干种常量:从编译期可知的值到解析运行才知道的值。每一个常量池都保存在方法区中,在类被加载到虚拟机后创建出来。显然,构造常量池不能超过方法区大小,否则抛OutOfMemoryError。

6:本地方法栈(Native Method Stacks)

  这块区域是为Java调用非Java编写的代码而开辟的空间,实际上就是一块传统语言用到的栈。如果虚拟机不支持native方法,自身也不依赖传统栈,可以不分配本地方法栈空间,如果支持,一般都在线程创建的时候分配空间。本地方法栈的大小调整和前面几个一致。分配的栈容量不得超出本地方法栈最大容量,否则抛StackOverflowError异常,无法申请足够的内存扩展栈的话,将抛出OutOfMemoryError异常。

  官方参考:link