本篇讲述JVM运行时数据区域。以JDK7为主。
本文1个TODO。
运行时数据区域
JVM运行时数据区.png程序计数器
字节码指令行号指示器。其记录的是字节码指令地址,用于控制选取下一条字节码指令。(包括:分支、循环、跳转、异常等)
特点
- 每条线程独享一个程序计数器。
- 只针对于JAVA方法有效,native方法时为undefined。
- 此区域是唯一一个在JVM规范中没有规定任何OOM错误的区域。
异常状况
JVM规范中无任何OOM错误说明。
ps:java的多线程是通过线程轮流切换并分配处理器执行时间来实现的。
Java虚拟机栈
JAVA内存模型的一部分。
特点
- 每个线程私有,生命周期和线程相同。
在java中,每个方法的执行都会创建一个栈帧,用于存储局部变量表
、操作数栈
、动态链接
、方法出口
等信息。每一个方法从调用到执行完成,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表,存放了各种基本类型(如int、double、long等8种)、对象引用、returnAddress类型。其中64位的long和double占2个局部变量空间(Slot),其余只占一个。局部变量所需空间在编译期
完成分配。当执行方法时,该方法需要多大的局部变量空间是完全确定的,运行时不会改变。
异常状况
该区域有两种异常状况:
- 如果线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError。
- 如果虚拟机展可以动态扩展,且扩展时无法申请到足够的内存,抛出OutOfMemoryError。
本地方法栈
和Java虚拟机栈一样,但不限于语言,其作用于虚拟机使用到的native方法。而Java虚拟机栈只限于Java方法。也会抛出StackOverflowError和OutOfMemoryError异常。
Java堆
用于存放所有的Java对象实例和数组。
特点
- JAVA虚拟机中内存最大的一块;
- 被所有线程共享;
- 启动是创建;
- 可以在物理上处于不连续的内存空间,只要逻辑连续即可。
Java堆分为新生代和老年代。新生代有Eden空间、From Survivor空间、To Survivor空间。
异常状况
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出OutOfMemoryError。
方法区
用于存储被JVM加载的类信息
、常量
、静态变量
、即时编译器编译后的代码
等。
特点
- 线程共享;
- “永久代”,该部分可以选择不GC;
- 可以在物理上处于不连续的内存空间,只要逻辑连续即可。
在方法区实际上是有GC的,GC的主要目标是对常量池的回收和对类型的卸载。因为该区域回收率过于低下,所以有部分人会选择不进行GC,而其回收率低也是被称为“永久代”的原因。
在以前的版本中,出现过因低版本的HotSpot未对该区域完全回收而导致的内存泄漏,所以以后改采用Native Memory来实现方法区的规划了。
//TODO:该部分需要修改,看一下是J8还是JDK9来完成该部分的。
在JDK7中,已经把字符串常量池从“永久代”中移出。
异常状况
当方法区无法满足内存分配需求时,抛出OutOfMemoryError。
运行时常量池
该池属于方法区的一部分。用于存放编译期生成的各种字面量和引用(包括符号引用和直接引用)。
特点
- 动态性,常量不一定只有编译期才能产生。
- JVM对其没有规范,各厂商之间的不同。
ps:在运行期间,String.intern()方法会产生常量。
符号引用,是对于类来说的;直接引用,是对于对象来说的。
异常状况
当常量池无法申请到内存时,抛出OutOfMemoryError。
直接内存
该部分并不是JVM的一部分。
在JDK1.4中引入的NIO,是一种基于通道与缓冲区的IO方式,其使用Native函数库直接在堆外分配内存,然后通过存储在Java堆中的DirectByteBuffer对象作为对这块内存的引用进行操作。其性能显著提高,因为避免了Java堆和Native堆往返复制数据。
异常状况
在设置-Xmx
等参数时,需要考虑到Java堆内存和直接内存不得大于系统物理内存,否则会出现OutOfMemoryError。
下一节:HotSpot虚拟机探秘。
网友评论