JVM内存模型复习
前言
JVM全称Java Virtual Machine,即Java虚拟机,是用于运行Java程序编译后的字节码文件。
JVM 模型是对于每个java语言的使用者来说都是需要掌握的知识点,在此做一个简单的复习备注。
当我们在编写完Java代码后,Java编译器会将java文件编译为.class文件,然后通过Class Loader将类信息加载到JVM中,最后JVM再去调用操作系统,这样,只要JVM正确执行了.class文件,就可以实现跨平台。
JVM内存模型
先上一张经典的图:
Java内存模型经典图.jpg主要包括几个部分:
- 程序计数器
- 栈
- 堆
- 本地方法栈
- 方法区
一、程序计数器
- 程序计数器是一块较小的内存,可以看做是当前线程所执行字节码的行号指示器。当字节码解释器工作时,会通过计数器获取下一条需要执行的字节码指令。
- 程序计数器是线程私有的,各个线程之间的程序计数器互不干扰。
- 如果一个线程正在执行的是Java方法,则程序计数器记录的是正在执行的字节码指令的地址;如果正在执行的是 native 本地方法,则程序计数器记录的是 Undefined .
程序计数器内存区是唯一一个在JVM规范中没有规定任何内存溢出情况的区域。
二、 栈
Java栈中保存了局部变量和方法参数等,同时和Java方法的调用,返回密切相关。
Java虚拟机栈也是线程私有的,生命周期和线程相同。
每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用到执行完成的过程,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
1. 异常
栈区域有可能会出现以下两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverflowError异常,如递归错误。
- 如果虚拟机在动态扩展时无法申请到足够的内存空间,将会排除OOM异常,例如死循环创建对象。
三、本地方法栈
本地方法栈和Java虚拟机栈比较相似,其不同在于Java虚拟机栈用于Java方法的调用,而本地方法栈用于Native本地方法的调用。
本地方法栈也是线程私有的。
四、 堆
- 堆是线程共享的一块内存区域,在虚拟机启动时创建。
- 对于绝大多数应用来说,Java堆是JVM所管理的内存中最大的一块,大部分的对象实例及数据会存放在此。
Java堆可以处于物理上不连续的内存空间中,只要逻辑上连续即可。
Java堆也是垃圾收集器管理的主要区域。堆中分为新生代,老年代等(内存回收机制)。
当堆中没有内存可分配时,就会抛出OOM异常。
五、 方法区
方法去同Java堆一样,也是线程共享的内存区域。用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
当方法区无法满足内存分配需求时,将抛出OOM异常。
六、 运行时常量池
首先要区分常量池和运行时常量池。
1. 常量池
常量池,即是class文件的常量池,是class文件的一部分。Java文件被编译成class文件之后,除了包含了类的版本,字段,方法,接口等描述信息,还包括存放编译期生成的各种字面量和符号引用的常量池。
字面量主要包括:文本字符串,被声明为final的变量,基本数据类型的值。
符号引用主要包括:类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。
2. 运行时常量池
运行时常量池是方法区的一部分,当类加载到内存中,JVM就会将class文件常量池中的内容存档到运行时常量池。
七、直接内存
JDK1.4之后加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式。它可以使用Native方法库直接分配堆外指定内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这种方式在一些场景中避免了在Java堆和Native堆中来回复制数据。
直接内存是Java堆外的,会直接向系统申请的一块内存空间(直接内存不属于虚拟机运行时数据区)。因此,直接内存的大小不受虚拟机的限制,只受本机内存的限制。通常访问直接内存的速度会快于访问堆的速度。
总结
JVM内存结构复习