JVM虚拟机运行时数据区结构分为:
其中方法区和堆是所有线程共享的内存区域,而Java栈、本地方法栈、程序计数器是线程私有的。
我们详细介绍运行时数据区的各个区域及其作用。
程序计数器:
一块较小的内存空间,字节码指示器工作时通过改变计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
JVM的多线程通过线程轮流切换并分配处理器执行时间的方式来实现,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令,为了保证线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
存储内容:
当线程执行一个Java方法时,计数器记录的是正在执行的JVM字节码指令的地址。如果执行的是Native方法,计数器则为空(Undefined)。
该内存区域是唯一一个在jvm规范中没有规定任何OutOfMemoryError情况的区域。
Java虚拟机栈:
线程私有,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
存储内容:
栈的存储可以说是局部变量表部分,存放了编译期可知的基本数据类型(boolean、byte、char、short、int、float、long、double),对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
异常状况:
1、线程请求的栈深度大于JVM所允许的深度,抛出*Error异常。
2、虚拟机栈可以动态扩展时,当无法申请到足够的内存,就会抛出OutOfMemoryError异常。
本地方法栈:
作用与虚拟机栈非常相似,区别在于虚拟机栈服务于执行Java方法,而本地方法栈服务于执行Native方法。它也会抛出*Error和OutOfMemoryError异常
Java堆:
Java虚拟机管理的内存中最大的一块,被所有线程共享的内存区域。虚拟机启动时创建,该区域的目的就是存放对象实例,几乎所有对象实例都在这里分配内存。Java堆也是垃圾收集器管理的主要区域,又被称为GC堆。
从收集器的分代收集法来看,Java堆还可以分为:新生代和老年代。Java堆可以处于物理上不连续的内存空间,只要逻辑上是连续的即可。
存储内容:
几乎所有对象实例
异常情况:
如果在堆中没有内存完成实例分配,并且堆无法再扩展时,将抛出OutOfMemoryError异常。
方法区:
与Java堆一样,是各个线程共享的内存区域。别名非堆(Non-Heap),目的是与Java对区分开
存储内存:
存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
Java虚拟机规范对这块区域限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。因此又有很多人将其趁为永久代。这个区域内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。
异常情况:
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
方法区有时候也被称为持久代(PermGen),所有对象在实例化后的整个运行周期内,都被存放在堆内存中。堆内存又被划分成不同的部分:伊甸区(Eden),幸存者区域(Survivor Sapce),老年代(Old Generation Space)。
方法的执行都是伴随着线程的。原始类型的本地变量以及引用都存放在线程栈中。而引用关联的对象比如String,都存在在堆中。
运行时常量池(Runtime Constant Pool):
方法区的一部分,存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中释放。
当常量池无法再申请到内存时会抛出OutOfMemoryError异常。
直接内存:
该区域并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但这部分内存被频繁使用也可能导致OutOfMemoryError异常。
本机直接内存分配不会受到Java堆大小的限制,但会受到本机总内存及处理器寻址空间的限制。配置虚拟机参数时,忽略直接内存,会使得各个内存区域总和大于物理内存限制,导致动态扩展时出现OutOfMemoryError异常。
OutOfMemoryErrors:
Exception in thread “main”: java.lang.OutOfMemoryError: Java heap space 原因:对象不能被分配到堆内存中 Exception in thread “main”: java.lang.OutOfMemoryError: PermGen space 原因:类或者方法不能被加载到持久代。它可能出现在一个程序加载很多类的时候,比如引用了很多第三方的库; Exception in thread “main”: java.lang.OutOfMemoryError: Requested array size exceeds VM limit 原因:创建的数组大于堆内存的空间 Exception in thread “main”: java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space? 原因:分配本地分配失败。JNI、本地库或者Java虚拟机都会从本地堆中分配内存空间。 Exception in thread “main”: java.lang.OutOfMemoryError: <reason> <stack trace>(Native method) 原因:同样是本地方法内存分配失败,只不过是JNI或者本地方法或者Java虚拟机发现
参考资料:
《深入理解Java虚拟机——JVM高级特性与最佳实践(第2版)》
《纯洁的微信-JVM系列文章》:http://www.ityouknow.com/jvm.html
由衷的感谢提供学习资料的前辈们!!!