JVM学习之运行时数据区
深入理解jvm看过很多遍,每次遇到问题总是要去翻书,今天开始把知识点记录在博文中,网上的JVM教程多如牛毛,我在此处只做记录用。以下总结内容来自-----《深入理解Java虚拟机_JVM高级特性与最佳实践》
java能够实现跨平台特性依靠java虚拟机和字节码文件,JVM利用垃圾回收算法实现内存的自动回收,简化了程序中内存的管理,使程序员摆脱了内存管理的束缚,但为了更好了编写高效程序,我们依然要了解java的内存划分和垃圾回收策略,今天学习jvm的运行时数据区结构。
java虚拟机会把自己管理的内存在逻辑上进行划分,分成不同区域,各个区域有各自的用途和创建销毁时机。
java运行时数据区结构图:
1 程序计数器(PC)
程序计数器是一块较小的内存,是当前线程所执行的字节码的行号指示器,JVM中字节码解释器解释字节码时是通过改变程序计数器的值来选取下一条需要执行的字节码指令的,分支,循环,跳转,异常处理,线程恢复等都需要程序计数器,线程切换后进行恢复需要到当前线程的PC位置。
特点:
- PC是线程私有的,生命周期和线程相同
- 如果线程正在执行一个java方法,pc指向正在执行的虚拟机字节码指令地址,如果正在执行native方法,pc指向为null。
- 不会出现OutOfMemoryError
- 一块极小的内存空间
总结程序计数器作用:
- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制。
- 多线程的情况下,线程切换后回到原来的位置记录。
2 java虚拟机栈
java虚拟机栈也是线程私有的,它的生命周期和线程的生命周期相同。
java虚拟机栈描述的是java方法执行的内存模型:
java中每个方法执行时都会创建一个栈帧(Stack Frame),栈帧中存储着:
局部变量表,操作数栈,动态链接,方法出口等信息。
当这个方法执行完毕后,这个方法所对应的栈帧将会出栈,并释放内存空间。
局部变量表:存放方法参数和局部变量
存放编译期可知的各种基本数据类型(java基本类型),对象引用(指向对象起始地址的引用指针,也可以指向一个代表对象的句柄或其他与对象相关的位置),return Address类型(指向了一条字节码指令的地址)。
64位长度的long和double会占用两个局部变量空间,其余数据类型只占用一个。
操作数栈:方法刚开始执行时操作数栈为空,方法执行过程中有各种字节码指令往操作数栈中写入和提取内容。
操作数栈的最大深度在编译时已经写入Code属性的max_stacks数据项中。
动态链接:
每个栈帧都包含一个纸箱运行时常量池中该栈帧所属方法的引用,持有这个引用就是为了支持方法调用过程中的动态链接。
Class文件的常量池中存在大量的符号引用,一部分在类加载阶段或者第一次使用就转化为直接链接,另一部分将在每一次运行期间转化为直接引用,这部分就是动态链接。
方法返回地址:
方法执行完,退出之后需要返回到方法被调用的位置,程序才能继续执行,所以方法返回地址就是为了帮助恢复它的上层方法的执行状态。
注意:
局部变量表所需的内存空间在编译期就完成了分配,进入一个方法时,生成的栈帧中分配的局部变量空间是确定的,运行期间不会改变。
操作数栈是线程的工作区,用来存放运行过程中生成的临时数据。
java虚拟机规范中,这个区域可能抛出StackOverflowError和OutOfMemoryError。
StatckOverFlowError:如果线程的请求的栈深度超过虚拟机所允许的深度。
OutOfMemoryError:如果无法申请到足够的内存。
3本地方法栈
类似虚拟机栈作用,作用于本地方法(native方法),这个区域可能抛出StackOverflowError和OutOfMemoryError异常。
4java堆(java Heap)
java堆相对于虚拟机的其他区域来说是最大的,被线程所共享,用于存放对象的实例,几乎所有的对象实例都在这里分配内存。
java堆也是垃圾收集器的主要作用区域:
java堆细分为新生代和老年代或者Eden空间,From Survivor空间和TO Survivor空间。
虽然是线程共享的,但每个线程会获得线程私有的分配缓冲区,但存储的依然是对象的实例。
堆特点:
- 线程共享
- 虚拟机启动时创建
- 主流虚拟机堆可扩展
- 会抛出OutOfMemoryError错误
- 垃圾回收的主要场所
- 内部细分为新生代,老年代
5方法区
方法区和堆一样是线程共享的内存区域,主要存储:
已被虚拟机加载的类信息,常量,静态变量和即时编译器编译后的代码等数据。java
虚拟机规范把方法区描述为堆得一个逻辑部分,但方法区与堆得区别还是蛮大的。
HotSpot上很多人愿意把方法区称为永久代,但本质上两者并不等价,HotSpot中GC分代收集扩展至方法区,垃圾收集器可以像管理java堆一样的管理这部分内存,对于其他虚拟机实现则不存在永久代,HotSpot也正逐步采用Native Memory来实现方法区。
6 运行时常量池
方法区的一部分,Class文件中除了有类的版本信息,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
包括String ,
7 直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,但这部分内存也被频繁的使用,可能导致OutOfMemoryError异常出现。
java中的NIO类就是操作直接内存的api,直接内存不会受到java堆大小的限制,但会受到本机总内存大小以及处理器寻址空间的限制。
jdk8堆方法区的修改:
jdk7开始,就开始了永久代的转移工作,将譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;
jdk8彻底移除了永久代,永久代才被元空间替代。
不会再出现java.lang.OutOfMemoryError: PermGen问题。
元空间又是什么呢?以前存储在永久代里面的数据现在存在了哪里?
元空间是一块与堆不相连的本地内存。原本存在永久代的数据,一部分移到了java堆里面,一部分移到了本地内存里面(即元空间)。
永久代中原来存储的字符串常量(池)、符号引用(这两个在jdk7普遍就已经将其放在堆上了)和类的静态变量现在存储在java堆中,其余的数据作为元数据存储在元空间中。
什么是元数据呢?
元数据是数据的数据或者叫做用来描述数据的数据或者叫做信息的信息。可以理解成最小的数据单位,包括元素属性(名称、大小、数据类型、等)等。