什么是运行时数据区?
运行时数据区域是怎么划分的?
每个部分有什么作用?
什么是运行时数据区?
在JVM执行Java程序的时候,会将JVM所管理的内存切分成若干个区域。 这些被JVM管理的内存区域就是运行时数据区域(Runtime Data Area)。
运行时数据区域是怎么划分的?
主要分为两大部分:
1) 每个线程分配的内存空间,线程独有,其他线程不能访问,生命周期跟随线程的生命周期。
2) 各个线程共享的内存空间,各个线程可以共同访问,生命周期跟随虚拟机生命周期。
运行时数据区域的划分可以用下图表示:
每个部分有什么作用?
首先看线程共享的两个区域。
Java堆:
- 堆是虚拟机管理的内存中最大的一块。
- 被所有线程共享,在虚拟机启动的时候创建。
- 唯一的目的就是存放对象实例,几乎所有的实例对象和数组都要在堆上分配,但不绝对。
- 垃圾收集器管理的主要区域。
- 可以处于物理上不连续的内存空间中。
- 堆中没有内存完成实例分配,并且堆无法再扩展时,将会抛出OutOfMemeryError异常。
堆空间继续细分下去的话,就会有新生代和老年代,还有永久代。注意永久代并不属于堆内存中的一部分,同时jdk1.8之后永久代也将被移除,改用metaspace实现。
这样的划分只是为了垃圾回收的时候更加高效,这样的分代是垃圾回收器的概念,CMS ,ParNew,G1 就会有分代的概念, 在ZGC里面,就不进行分代了。
方法区:
- 也叫做非堆(Non-heap),被各个线程共享
- 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 不需要连续的物理内存,可以选择固定大小或可扩展
- 可以选择不实现垃圾收集
- 相对较少执行垃圾回收,主要是针对常量池的回收和类型的卸载,回收条件较为苛刻
- 无法满足内存分配需求时,将抛出OutOfMemeryError异常
运行时常量池:
- 在JDK1.7中,常量池是在方法区中,JDK1.8就放到了堆里面了。
- Class文件中,有一项信息叫做常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放
- 运行时也可以将新的常量放入池中
- 常量池无法申请到内存时,将会抛出OutOfMemeryError异常
再看每个线程独立的内存区域。
程序计数器(PC):
这个是一个比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。Java多线程分配策略是时间片轮转的策略,所以每个线程都需要建立独立的程序计数器,各个线程之间的计数器互相独立,才能保证,线程恢复执行的时候的字节码行号的准确性。
- 线程独立,生命周期与线程相同,每个线程有自己独立的程序计数器。
- 如果线程正在执行Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址。
- 如果线程正在执行Native方法,计数器则为空(Undefined)。
- 唯一一个虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
Java虚拟机栈:
虚拟机栈描述的是Java方法执行的内存模型。每个方法执行的同时,都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完成的整个过程,对应一个栈帧在虚拟机栈中入栈到出栈的过程。
- 线程私有的,生命周期与线程相同。
- 局部变量表存放了编译器可知的各种基本数据类型,对象引用和returnAddress类型(指向了一条字节码指令的地址)。
- 线程请求的栈的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
- 如果虚拟机可以动态扩展,当扩展的时候无法申请足够的内存,将抛出OutOfMemeryError异常。
本地方法栈:
和Java虚拟机栈很相似,不同的点是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,本地方法栈则为虚拟机使用到的Native方法服务。
甚至有的虚拟机会将虚拟机栈和本地方法栈合二为一。
本地方法的说明:https://csming1995.github.io/2017/03/12/JVM%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-1/
其实细分下来,每一个部分还是有很多内容需要深入去了解的。如果不结合其他知识,比较难以理解,所以本文并不深入。
参考:《深入理解Java虚拟机》第二版