1 运行时数据区域
在 JVM 中,JVM 内存主要分为堆
、程序计数器
、方法区
、虚拟机栈
和本地方法栈
等。
按照与线程的关系也可以这么划分区域:
线程私有区域
:一个线程拥有单独的一份内存区域。线程共享区域
:被所有线程共享,且只有一份。
1.1 程序计数器
程序计数器
(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程
所执行的字节码的行号指示器
。主要用来记录各个线程执行的字节码的地址
,例如,分支
、循环
、跳转
、异常
、线程恢复
等都依赖于计数器。
各线程之间独立存储,互不影响(线程私有
)。
如果线程正在执行的是一个
Java方法
,这个计数器记录的是正在执行的虚拟机字节码指令的地址
;
如果正在执行的是本地(Native)方法
,这个计数器值则应为空
(Undefined)。
此内存区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError
情况的区域。
1.2 栈
1.2.1 Java虚拟机栈
与程序计数器
一样,Java虚拟机栈
(Java Virtual Machine Stack)也是线程私有
的,它的生命周期与线程相同。
虚拟机栈描述的是Java方法执行的线程内存模型
:每个方法被执行的时候,Java虚拟机
都会同步创建一个栈帧
(Stack Frame)用于存储局部变量表
、操作数栈
、动态连接
、方法出口
等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧
在虚拟机栈中从入栈到出栈的过程
。
1.2.1.1 局部变量表
比如方法中的变量
,存放了编译期
可知的各种基本数据类型
(boolean、byte、char、short、int、float、long、double)、对象引用
(reference类型) 和 returnAddress类型
(它指向了一条字节码指令的地址);
1.2.1.2 操作数栈
存放java 方法执行的操作数
的。
操作数栈本质上是JVM 执行引擎的一个工作区,也就是方法在执行,才会对操作数栈进行操作,如果代码不执行,操作数栈其实就是空的。
虚拟机栈的大小缺省为1M,可用参数
-Xss
调整大小,例如-Xss256k.1.2.1.3 动态链接
部分符号引用
在运行期间
转化为直接引用
,这种转化就是动态链接
。
1.2.1.4 返回地址(方法出口)
正常返回(调用程序计数器中的地址作为返回):
- 恢复上层方法的
局部变量表
和操作数栈
; - 把
返回值
(如果有的话)压入调用者栈帧的操作数栈
中; - 调整
程序计数器
的值以指向方法调用指令后面的一条指令。
异常的话(通过异常处理器表<非栈帧中的>来确定)。
1.2.2 本地方法栈
本地方法栈
用于管理本地(Native)方法
的调用。
本地方法
并不是用Java
实现的,而是由C
语言实现的(比如Object.hashcode 方法)。
HotSpot
直接把本地方法栈
和虚拟机栈
合二为一。
1.3 堆
堆是 JVM 上最大的内存区域
,我们申请的几乎所有的对象,都是在这里存储的。我们常说的垃圾回收,操作的对象就是堆
。
堆空间一般是程序启动时,就申请了,但是并不一定会全部使用。堆一般设置成可伸缩的。
那一个对象创建的时候,到底是在堆上分配,还是在栈上分配呢?
这和两个方面有关:对象的类型
和在 Java 类中存在的位置
。
Java 的对象可以分为
基本数据类型
和普通对象
。
- 对于
普通对象
来说,JVM 会首先在堆
上创建对象,然后在其他地方使用的其实是它的引用
。比如,把这个引用保存在虚拟机栈的局部变量表中。 - 对于
基本数据类型
来说(byte、short、int、long、float、double、char),有两种情况:- 当你在
方法体内
声明了基本数据类型
的对象,它就会在栈
上直接分配。 - 其他情况,都是在
堆
上分配。
- 当你在
1.3.1 堆大小参数
-Xms
:堆的最小值;-Xmx
:堆的最大值;-Xmn
:新生代的大小;-XX:NewSize
:新生代最小值;-XX:MaxNewSize
:新生代最大值。
1.3.2 Young Generation
年轻代
被分为三个部分:Eden
、Survivor 1
、Survivor 2
(被称为from/to
或s0/s1
),默认比例是8:1:1
。
- 大多数新创建的对象都位于
Eden
内存空间中; - 当
Eden
空间被对象填充时,执行Minor GC
,并将所有Survivor
对象移动到另一个Survivor
空间中; Minor GC
检查幸存者对象,并将它们移动到另一个Survivor
空间。所以每次,一个Survivor
空间总是空的;- 经过多次
GC 循环
后存活下来的对象被移动到老年代
。通常,这是通过设置年轻代对象
的年龄阈值
来实现的,然后他们才有资格提升到老年代。(当然还有其他晋升渠道,后面会讲。)
1.3.3 Old Generation
旧的一代内存包含那些经过许多轮小型 GC 后仍然存活的对象。
大对象
直接进入老年代
(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在 Eden 区
和两个Survivor 区
之间发生大量的内存拷贝。
1.4 方法区(JDK1.7及以前)
方法区
(Method Area)是可供各条线程共享的运行时
内存区域。用于存储已被虚拟机加载的类型信息
、常量
、静态变量
、即时编译器编译后的代码缓存
等数据。
1.4.1 运行时常量池
运行时常量池
(Runtime Constant Pool)是方法区
的一部分。Class文件
中除了有类的版本
、字段
、方法
、接口
等描述信息外,还有一项信息是常量池表
(Constant Pool Table),用于存放编译期
生成的各种字面量
与符号引用
,这部分内容将在类加载后
存放到方法区的运行时常量池
中。
Tips:
字面量
:指字母
、数字
等构成的字符串
或者数值常量
,只可以以右值
出现。所谓右值
是指等号右边的值,如int a = 1
, 这里的a
是左值,1
为右值。在这个例子中1
就是一个字面量
。符号引用
:是相对于直接引用
来说的,如类和接口的全限定名
、字段名称和描述符
、方法名称和描述符
、方法句柄和方法类型
、动态调用点和动态常量
都是符号引用
。Class 常量池
:用于存放编译期
生成的各种字面量
(Literal)和符号引用
(Symbolic References),常量池一旦被装入内存就变成了运行时常量池
,对应的符号引用
在程序加载或运行时就会被转变为被加载内存区的代码的直接引用
(即动态链接
)。Java虚拟机对于Class文件每一部分(自然也包括常量池)的格式都有严格规定,如每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、加载和执行,但对于
运行时常量池
,《Java虚拟机规范》并没有做任何细节的要求
,不同提供商实现的虚拟机可以按照自己的需要来实现这个内存区域,不过一般来说,除了保存Class文件中描述的符号引用外,还会把由符号引用
翻译出来的直接引用
也存储在运行时常量池
中。
运行时常量池
相对于Class文件常量池
的另外一个重要特征是具备动态性
,Java语言并不要求常量一定只有编译期
才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间
也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类
的intern()
方法。
1.5 元空间(JDK1.8及以后)
在JDK1.8
中,Oracle最终删除了方法区,转而替代的方案是采用元空间
,使得Hotspot
与JRockit
完美融合。另外,也能解决永久代更容易遇到内存溢出的问题
。因为元空间
存储位置是本地内存
(堆外内存)。
早在
Java7
版本中,Oracle就已经将永久代的静态变量
和字符串常量池
转移到了堆
中。
其初始和最大值通过-XX:MetaspaceSize
、-XX:MaxMetaspaceSize
进行控制。
1.6 直接内存
直接内存
(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError
异常出现,所以我们放到这里一起讲解。
如果使用了NIO
,这块区域会被频繁使用。在Java堆内
可以用directByteBuffer
对象直接引用
并操作
。
这块内存不受Java堆大小限制
,但受本机总内存的限制
,通过-XX:MaxDirectMemorySize
来设置(默认与堆内存最大值一样)。
1.7 版本变化
JDK版本 | 是否有永久代,字符串常量池放在哪里? | 方法区逻辑上规范,由哪些实际的部分实现的? |
---|---|---|
jdk1.6及之前 | 有永久代,运行时常量池 (包括字符串常量池),静态变量 存放在永久代上 | 这个时期方法区在HotSpot 中是由永久代来实现的,以至于这个时期说方法区就是指永久代 |
jdk1.7 | 有永久代,但已经逐步“去永久代”,字符串常量池 、静态变量 移除,保存在堆 中; | 这个时期方法区在HotSpot 中由永久代 (类型信息、字段、方法、常量)和堆 (字符串常量池、静态变量)共同实现 |
jdk1.8及之后 | 取消永久代 ,类型信息 、字段 、方法 、常量 保存在本地内存的元空间 ,但字符串常量池 、静态变量 仍在堆 中 | 这个时期方法区在HotSpot 中由本地内存的元空间 (类型信息、字段、方法、常量)和堆 (字符串常量池、静态变量)共同实现 |
只有
HotSpot
才有永久代
的概念.