JVM基础
1.基础
JDK 将java文件编译成class文件
JRE 包含JVM
JVM可以进行内存管理
利用JDK(调用JAVA API)开发了属于我们自己的JAVA程序后,通过JDK中的编译程序(javac)将我们的文本java文件编译成JAVA字节码,在JRE上运行这些JAVA字节码,JVM解析这些字节码,映射到CPU指令集或OS的系统调用。
2.JVM运行时数据区
2.1程序计数器
记录当前线程所执行的字节码行号,用于获取下一条执行的字节码。
当多线程运行时,每个线程切换后需要知道上一次所运行的状态、位置。由此也可以看出程序计数器是每个线程私有的。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是Native方法,这个计数器值则为空(Undefined)。此内存区域是唯一个在Java虚拟机规范中没有规定任何OurOfMemoryError情况的区域。
引申参考:什么是程序计数器
2.2虚拟机栈
存储当前线程运行方法所需要的数据、指令、返回地址。
虚拟机栈是有一个一个的栈帧组成,栈帧是在每一个方法调用时产生的。
每一个栈帧由局部变量表
、操作数栈
等组成。每创建一个栈帧压栈,当一个方法执行完毕之后则出栈。
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和returnAddress类型(指向了一条字节码指令的地址)。其中64位长度的long和double类型的数据会占用2个局部变量空(slot), 其余的数据类型占1个。局部变量表所需的内存空间在编译期间分配完成,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。如果线程请求栈的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;无法申请到内存抛出OutOfMemoryError异常。
如果出现方法递归调用出现死循环的话就会造成栈帧过多,最终会抛出
stackoverflow
异常。
这块内存区域也是线程私有的。
2.3本地方法栈
为线程所私有,功能和虚拟机栈非常类似。线程在调用本地方法时,来存储本地方法的局部变量表,本地方法的操作数栈等等信息。
2.4堆
Java
堆是整个虚拟机所管理的最大内存区域,所有的对象创建都是在这个区域进行内存分配。
这块区域也是垃圾回收器重点管理的区域,由于大多数垃圾回收器都采用分代回收算法
,所有堆内存也分为 新生代
、老年代
,可以方便垃圾的准确回收。
这块内存属于线程共享区域。
2.5方法区
方法区主要用于存放已经被虚拟机加载的类信息,如常量,静态变量
。 这块区域也被称为老年代
。是各个线程共享的内存区域。
运行时常量池
- 运行时常量池是方法区的一部分,所以也是全局共享的。
- 其作用是存储 Java 类文件常量池中的符号信息。
- class 文件中存在常量池(非运行时常量池),其在编译阶段就已经确定;JVM 规范对 class 文件结构有着严格的规范,必须符合此规范的 class 文件才会被 JVM 认可和装载。
- 运行时常量池 中保存着一些 class 文件中描述的符号引用,同时还会将这些符号引用所翻译出来的直接引用存储在 运行时常量池 中。
- 运行时常量池相对于 class 常量池一大特征就是其具有动态性,Java 规范并不要求常量只能在运行时才产生,也就是说运行时常量池中的内容并不全部来自 class 常量池,class 常量池并非运行时常量池的唯一数据输入口;在运行时可以通过代码生成常量并将其放入运行时常量池中。
- 同方法区一样,当运行时常量池无法申请到新的内存时,将抛出 OutOfMemoryError 异常
2.6直接内存
直接内存不是虚拟机运行时数据区的一部分。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。在JDK1.4中新加入了NIO类,引入了一种基于通道与缓存区(buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。