文章目录
- 内存区域划分
- 1.Java8 之前
- 2.Java8及之后
- 各块区域解释
- 1. 程序计数器
- 2. `Java虚拟机栈`
- 3. 本地方法栈
- 4. `堆`
- 5. 方法区
- 6. 运行时常量池
- 7. 直接内存
- 例子
- StackOverflow
- OutOfMemory
内存区域划分
1.Java8 之前
2.Java8及之后
各块区域解释
1. 程序计数器
- 程序计数器(Program Counter Register)是一块
较小
的内存空间,它可以看作是当前线程所执行的字节码
的行号指示器
。 - 字节码解释器工作时就是通过改变这个计数器的值来选取
下一条需要执行的字节码指令
,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 - 如果线程正在执行的是一个
Java方法
,这个计数器记录的是正在执行的虚拟机字节码指令的地址
;如果正在执行的是本地(Native)方法
,这个计数器值则应为空
。 - 该区域是
唯一不会发生OutOfMemory
的区域。
2. Java虚拟机栈
- 每个
Java方法被执行
的时候,Java虚拟机都会同步创建一个栈帧
(Stack Frame)用于存储局部变量表
、操作数栈、动态连接、方法出口等信息。 - Java虚拟机栈存放的就是
一个个栈帧
。 -
基本数据类型
、对象引用
就储存在局部变量表中。 - 如果线程请求的栈深度大于JVM设定的虚拟机栈的最大深度,那么就会产生
StackOverflow
错误。 -
HotSpot
虚拟机不允许栈容量扩展,因此不会引发OutOfMemory错误
。
3. 本地方法栈
- 与Java虚拟机栈类似,区别是本地方法栈用于执行Native方法,Java虚拟机栈用于执行Java方法。
- HotSpot虚拟机将Java虚拟机栈与本地方法栈合并。
4. 堆
- 内存区域中
最大
的一块,线程共享
。 - 存放
对象实例
、数组
-
垃圾收集器
工作的区域
思考:对象的成员变量储存在哪里?
对象的成员变量都储存在堆上,只有进入方法时,局部变量会存放在栈上。
也就是说基本数据类型、引用类型存在于堆和栈上都可以。
5. 方法区
- 储存
Class信息
、常量
、静态变量
- Java8之前方法区位于
堆
中,Java8及之后,方法区被取消,使用位于直接内存的元空间
代替方法区。 - Java8之前,HotSpot虚拟机定义
永久代
作为JVM方法区的实现。 - 方法区容量不够时会产生
OutOfMemory
错误。
6. 运行时常量池
- 方法区的一部分,储存
字面量
和符号引用 - 字面量包括:
文本字符串
、基本数据类型的值
、final常量值
7. 直接内存
- 即实际机器内存,
Native
函数库中提供了直接分配堆外内存的接口 - 这部分内存也可能出现OutOfMemory错误。
例子
//待完善:使用jmap
等工具监控内存使用
StackOverflow
- 栈溢出定义
如果线程请求分配的栈容量
超过java虚拟机栈允许的最大容量的时候,java虚拟机将抛出一个StackOverFlowError异常。 - 造成栈溢出的情况
- 方法递归调用过深
- 定义了大量的本地变量,增大此方法帧中本地变量表的长度。
- JVM调整栈容量的命令
-Xss
OutOfMemory
- 堆溢出
通过设置-Xmx
与-Xms
调整堆的最大最小值 - 方法区溢出
Java8之前方法区在堆上,可以设置-XX:PermSize
和-XX:MaxPermSize
调整永久代的大小,Java8及之后,可以设置-XX:MaxMetaspaceSize
和-XX:MetaspaceSize
调整元空间大小 - 直接内存溢出
可以通过-XX:MaxDirectMemorySize
参数来指定,如果不去指定,则默认与Java堆最大值(由-Xmx指定)一致。