Java运行时数据区域
数据区域主要分为五个部分:
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 堆
- 方法区
其中所有线程共享区域有:方法区和堆。
每个线程独享区域有:虚拟机栈,本地方法栈,程序计数器
线程独享的区域
程序计数器
程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器,如果当前线程执行的是一个本地方法,那么此时计数器的值为undefined
,是唯一一个不会出现OutOfMemoryError
的内存区域。
Java虚拟机栈
虚拟机栈描述的是Java方法执行的线程内存模型,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧,用于存放以下内容:
- 局部变量表
- 操作数栈
- 动态链接
- 方法出口
其中局部变量表包括以下内容:
- 八大基本数据类型
- 对象引用
- returnAddress类型(保存的是return后要执行的字节码的指令地址)
这些数据类型在局部变量表中的存储空间以局部变量槽来表示,64位长度的long
和double
类型占用两个变量槽,其余的占用一个。局部变量表所需的内存空间在编译期间完成分配,方法运行期间不会改变局部变量表的大小。
Java 虚拟机栈会出现两种异常:StackOverFlowError
和 OutOfMemoryError
。
- 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出
StackOverFlowError
- 如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够内存会抛出
OutOfMemoryError
本地方法栈
本地方法栈是为JVM使用到的Native
方法准备的空间。
线程共享的区域
堆
主要特点:
- 堆是被所有线程共享的一块内存区域
- 在虚拟机启动时创建
- 堆的唯一目的就是存放对象实例
- 堆是垃圾回收管理的内存区域
- 可分为新生代,老年代,永久代等(一部分垃圾收集器的共同特性或者设计风格,不是具体实现的固有内存布局)
- 从分配内存的角度看,所有线程共享的Java堆可以划分出多个线程私有的分配缓冲区(TLAB)以提升对象分配的效率,这样做可以更好地回收内存或者更快的分配内存。
- 堆的大小可以通过
-Xmx
和-Xms
设定。
方法区
Java 虚拟机规范中定义方法区是堆的一个逻辑部分。 用于存储以下内容:
- 已被虚拟机加载的类信息
- 常量
- 静态变量
- 即时编译器编译后的代码
方法区内的主要回收目标是对常量池的回收和对类型的卸载。
运行时常量池
运行时常量池是方法区的一部分。主要存放以下内容:
- 类信息(版本,字段,方法,接口等描述信息)
- 常量池表(用于存放编译期生成的各种字面量与符号引用)
Java并不要求常量一定在编译期间产生,并非Class文件中常量池的内容才能进入运行时常量池,运行期间产生的新的常量也能放入池中。比如String
类的intern()
方法。
当常量池无法再申请到内存会抛出OutOfMemoryError
直接内存(堆外内存)
直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是会被频繁地使用,也可能导致OutOfMemoryError
直接内存的分配不受JVM控制,但是会受到本机总内存(物理内存,SWAP分区,分页文件)以及处理器寻址空间的限制。