Java虚拟机学习(1)—Java内存区域划分

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。

一说到Java的内存区域,经常有人笼统地划分为堆内存和栈内存,堆存放对象,栈存放局部变量和方法。其实事实上Java的内存区域划分要比这复杂。不同的区域各自用途不同,创建、销毁、收集的时间和方法也各不相同。

根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存包括以下几个数据区域:
Java虚拟机学习(1)—Java内存区域划分

1. 程序计数器

程序计数器是每个线程所独有的一块较小的内存区域,用来作为当前执行的字节码的行号指示器(概念上感觉很像CPU里的计数器),Java线程的分支、循环、跳转、异常处理、线程恢复都依赖于它。

特别的,如果线程正在执行本地(Native)方法,程序计数器值为空。该内存区域也是唯一不会出现OOM异常的Java内存区域。

2. Java虚拟机栈

这就是上文说到的栈内存,也是线程所私有的内存区域,存放Java方法执行的内存模型。一个方法对应一个栈帧,栈帧里包括局部变量表、操作数栈、动态连接、方法出口等信息。因为方法之间经常会互相调用,可以发现一个方法从执行到结束的过程就形象的对应了一个栈桢入栈到出栈的过程。

该内存区域存在两种异常:如果线程请求栈深度大于内存限制,则抛出*Error异常;如果Java虚拟机栈可以动态扩展,当无法申请到足够的内存时会抛出OOM异常(常用虚拟机HotSpot并不支持动态扩展)。

3. 本地方法栈

与Java虚拟机栈类似,上者为Java方法服务,而本地方法栈则为本地方法(非Java代码)服务,并且本地方法栈存在的异常也与Java虚拟机栈相同。

特别的,有些虚拟机将Java虚拟机栈和本地方法栈合二为一,比如HotSpot。

4. Java堆

Java堆是所有线程共享的一块内存区域,也是虚拟机管理的最大的一块区域,虚拟机启动时创建,负责存放对象实例。

Java堆是垃圾收集器管理的内存区域,因此也被称作GC堆。尽管在实际应用中Java堆还会被细分为“新生代”、“老生代”或线程私有的缓冲区域,但这些划分都是不同虚拟机为了便于内存分配与垃圾回收而自行定义的,《Java虚拟机规范》并没有对该内存区域的实现做进一步的要求。

目前主流虚拟机都支持扩展Java堆内存(使用参数-Xmx和-Xms设定),如果Java堆内存不足以完成对象实例分配,并且堆也无法再扩展时,抛出OOM异常。

5. 方法区和运行时常量池

方法区线程共享,存放被虚拟机加载的类型信息、常量、静态变量、代码缓存等信息,类似于Java堆的逻辑部分,和堆相互配合。

运行时常量池属于方法区的一部分,用于存放编译和运行时产生的字面量和符号引用,具备动态性(运行时可动态增加字面量,本身也可动态扩展内存)。

两者没有足够内存完成分配需求时,都抛出OOM异常。

6. 直接内存 *

直接内存并不属于JVM内存部分,它是Native函数库直接分配堆外内存,再使用Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,目的是提升性能,避免在Java堆和Native堆中来回复制数据。

直接内存不受JVM内存上限限制,但当物理实际内存不足以分配时,依然抛出OOM异常。

更多精彩文章请访问我的个人博客(zhuoerhuobi.cn)