JVM内存模型初探

191 阅读6分钟

JVM运行时的数据区

线程隔离区:线程私有,其各自区域的生命周期与线程相同

jVM运行时的数据区

1、程序计数器

程序计数器(Program Counter Register)是当前线程所执行的字节码的行号指示器。

字节码解释器工作时就是通过改变这个计数器 的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一 个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

注意:1、当线程正在执行Java方法,程序计数器记录的是正在执行的虚拟机字节码指令的地址;

​ 2、当线程正在执行Native(本地)方法,程序计数器为空(Undefined)。【此区域为《Java虚拟机规范》中,唯一未规定的OutOfMemoryError 情况区域】

2、Java虚拟机栈

Java虚拟机栈(Java Virtual Machine Stack)是Java方法执行的线程内存模型。

每个方法被执行的时候,Java虚拟机都会同步创建一个**栈帧(Stack Frame)**用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

虚拟机栈通常只是指虚拟机栈中局部变量表部分。

局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。

在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的long和 double类型的数据会占用两个变量槽,其余的数据类型只占用一个。

在《Java虚拟机规范》中,对这个内存区域规定了两类异常状况:

1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;

2、如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。

3、本地方法栈

本地方法栈(Native Method Stacks)是为虚拟机使用到的本地(Native)方法服务的区域。

《Java虚拟机规范》对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机(譬如Hot-Spot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowErrorOutOfMemoryError异常。

注意:Hot-Spot虚拟机直接就把本地方法栈和虚拟机栈合二为一。

4、Java堆

Java堆(Java Heap) 是被所有线程共享的一块内存区域,在虚拟机启动时创建。 其唯一目的就是用来存放对象实例。

java堆又称“GC堆”(Garbage Collected Heap),是垃圾收集器管理的内存区域。

《Java虚拟机规范》中描述java堆:“ 所有的对象实例以及数组都应当在堆上分配 ”

The heap is the runtime data area from which memory for all class instances and arrays is allocated。

Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该 被视为连续的,这点就像我们用磁盘空间去存储文件一样,并不要求每个文件都连续存放。但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间。

5、方法区

方法区(Method Area)是用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据的区域。

JDK8之前,HotSpot虚拟机是使用永久代来实现方法区,这种设计容易导致java应用发生内存溢出的问题(永久代有-XX:MaxPermSize的上限,即使不设置也有默认大小)。到了JDK8改用与JRockit、J9一样在本地内存中实现的元空间(Meta- space)来代替,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。

《Java虚拟机规范》对方法区的约束是非常宽松的,可以选择不实现垃圾收集,垃圾收集行为在方法区很少见,该区域的内存回

收目标主要是针对常量池的回收和对类型的卸载

5.1、运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。

Class文件中除了有类的版本、字 段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

6、直接内存

直接内存 (Direct Memory)是JDK 1.4中新加入的NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的 DirectByteBuffer对象作为这块内存的引用进行操作。

该内存可显著提高性能,避免了某些情况在Java堆和Native堆中来回复制数据。

注意:一般服务 器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,但经常忽略掉直接内存,使得 各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现 OutOfMemoryError异常。