JVM(4)—运行时数据区
JVM(4)—运行时数据区
运行时数据区的组成

- 线程私有
- 程序计数器:存储线程的执行位置。
- 虚拟机栈:存储Java方法调用与执行过程的数据。
- 本地方法栈:存储本地方法的执行数据。
- 线程共享
- 堆:主要存储对象。
- 方法区:存储描述类/方法/字段等定义数据。
- 运行时常量区:保存常量数据。
程序计数器
-
记录当前线程所即将执行的字节码指令行号(即将要执行哪一行字节码指令)。
-
每一个线程拥有自己的计数器。
-
执行native本地方法时,程序计数器值为空。
-
占用内存极少,不会出现OOM(Out Of Memory Error)。
-
多线程时,CPU时间片切换后,程序计数器可以告诉CPU当前程序执行到了什么位置。

虚拟机栈
-
每一个栈帧对应了一个方法,每次方法调用都会产生对应的栈帧。
-
虚拟机栈生命周期与线程相同,所有方法执行完后,虚拟机栈就被销毁了,线程也结束了。
-
线程私有。
-
不会被垃圾回收。
-
栈深度(栈帧的数量,3个栈帧栈深度就是3)有限制。1.5后默认每一个线程的栈空间为1mb,之前256k。-Xss数值决定了栈的内存大小,决定栈的最大深度。
-
栈设置了固定大小(推荐)可能会发生 StackOverflowError:达到上限。
栈可以动态扩展可能会发生 OutOfMemoryError:可用内存不足。

栈帧的组成
- 局部变量表:局部变量
- 操作数栈:保存中间计算的临时结果
- 动态链接:将符号引用转为直接引用
- 返回地址:存放调用方法的程序计数器的值

局部变量表
-
按定义顺序存储方法的参数与方法内的局部变量。
-
线程私有,方法调用被创建,方法退出时销毁。
-
编译期间长度已经确定,局部变量元数据存储在字节码中。
-
是栈帧中最主要的存储空间,决定了栈的深度。
-
存储局部变量的单位为Slot(变量槽)。构造方法和实例方法中,0号槽位默认是this,指向当前类的实例。静态方法中没有this关键字。
-
32位以内的类型(int/float/char/引用类型...)占1个Slot,64位类型(long/double)占2个Slot。
(Start PC:字节码指令起始行号,给局部变量分配槽。Length:字节码指令作用了x行,释放槽)


- 槽复用:将原本空闲出来的槽复用给新来的局部变量,来缩小局部变量表的尺寸。

操作数栈
字节码指令在执行过程中的中间计算结果存储在操作数栈中。
push压入操作数栈、store_1将操作数栈栈顶数值存入局部变量表的1号槽中、load_1读取局部变量表1号槽中的值压入操作数栈、add将操作数栈栈顶与第二个的值取出相加放入操作数栈栈顶、return将操作数栈栈顶数值取出返回。

64位数据类型占2个操作数栈空间。

动态链接
将保存在字节码中的符号引用转为运行时内存的直接引用。内存指针,指向方法区中方法的元数据。

方法返回地址
保存该方法调用者的程序计数器的值,用于方法执行后后续执行。

本地方法栈
native本地方法
native本地方法就是Java调用非Java代码的接口。
定义native本地方法时,不需要提供方法的实现。
native本地方法可以调用其他语言接口实现对操作系统更底层的操作。
native本地方法栈

HotSpot将本地方法栈与虚拟机栈合二为一。
堆
- 存放运行时实例化的对象实例。new 出来的对象。
- JVM启动时就创建,堆内存也被分配。是JVM内存主要占用区域。
- 线程共享。堆内存在物理上可以分散,在逻辑上连续。
- 堆中包含线程私有的缓冲区(TLAB)用于提高JVM的并发处理效率。
JDK1.8 堆结构

- 新生代:用来存放新生的对象,该区域对象会被频繁GC。
- 老年代:新生代中的稳定对象放入老年代,该区域不会特别频繁GC。
- 元空间:内存的永久保存区域,主要存放类、方法的描述信息(元数据),几乎不GC。
设置堆的参数
- 设置堆空间大小参数
-
-Xms 设置堆空间(新生代+老年代)的初始内存大小。
-
-Xmx 设置堆空间(新生代+老年代)的最大内存大小。
- 默认堆空间大小
-
初始内存大小 :物理电脑内存大小 / 64
-
最大内存大小 :物理电脑内存大小 / 4
- 新生代与老年代的占用比例
-
新生代占用1/3内存。
-
老年代占用2/3内存。
- 手动设置堆空间
- -Xms1g -Xmx1g
- 建议将初始堆内存与最大堆内存设为相同的值。
- 建议Xms与Xmx的值为老年代FillGC后存活对象的3-4倍。
- -XX:+PrintGCDetails
对象分配
- 绝大数刚创建的对象会存入新生代的Eden伊甸园区。
- Eden伊甸园区与S0/S1的内存比例为 8:1:1 。

- Eden满时,会对年轻代进行垃圾回收,称为MinorGC。MinorGC的执行过程(复制交换算法):
- 扫描Eden、From、To区域所有对象。
- 在Eden区中如果对象可达(有引用),就给此对象添加个age=1的属性,将此对象复制到To区。
- 在From区中如果对象可达,就给此对象age的值加1,将此对象复制到To区。
- 清空Eden区与From区所有对象,From区变To区,To区变From区。
- 当n次MinorGC后,对象的age超过了15(阈值),对象会从From区晋升到老年区。

MinorGC/FullGC/MajorGC
- 任何GC都会出发STW(Stop The World,全局停顿)。
- MinorGC只针对年轻代回收,执行效率高。
- FullGC,全堆回收,针对年轻代、老年代、方法区全面收集,执行效率低下,会导致系统长时间停滞,减少FullGC次数是JVM优化重点。
- MajorGC,只针对老年代回收,CMS垃圾收集器才会存在MajorGC。
GC日志分析
[GC (Allocation Failure) [PSYoungGen: 8192K->1000K(9216K)] 8192K->6792K(60416K), 0.0027928 secs] [Times: user=0.06 sys=0.03, real=0.00 secs]
// GC : 不加类型默认为MinorGC
// (Allocation Failure) : 产生GC的原因,Eden空间分配不足
// PSYoungGen : GC的范围是年轻代
// 8192K->1000K(9216K) : GC前年轻代占用的空间->GC后年轻代占用的空间(年轻代总空间),MinorGC后只有To区域有对象。
// 8192K->6792K(60416K) : GC前堆占用的空间->GC后堆占用的空间(堆总空间)
// 0.0027928 secs : 本次GC使用时间
为什么年轻代总空间是9216K,而不是10240K?
答:年轻代组成是1个Eden和2个Survivor,只一个Survivor存放对象,所以总空间为Eden空间8192+1个Survivor空间1024=9216.
方法区
- 线程共享,物理上可以分散存储,逻辑为整体的内存区域。
- 保存了类加载信息、类信息(包含字段、方法)、即时编译器编译后的代码缓存、常量、静态变量(1.6版本以前)
- 方法区只是个概念,具体实现看JVM的不同的厂商。HotSpot的方法区实现为永久代(1.7版本),元空间(1.8版本)。
永久代与元空间的区别
- 规范说明方法区是堆的逻辑部分,但实现与堆内存无关,称为“非堆”。
- 永久代是jdk7和之前版本的方法区实现。
- 特点:占用JVM内存保存数据,-XX:MaxPermSize有上限,加载的类太多会容易内存溢出,与其他JVM实现不一致。
- 元空间是jdk8之后的方法区实现。
- 特点:使用本地内存保存数据,最大为可用物理内存,与其他JVM实现一致。
设置元空间
- -XX:MetaspaceSize=xxx
设置元空间初始大小,默认大小与平台相关,到达进行调整并自动触发FullGC。
建议调整为一个较大的值,减少FullGC的可能。
- -XX:MaxMetaspaceSize=xxx
设置元空间最大尺寸,默认为-1,代表最大可用内存,一般不设置最大值。
- 超过最大值后会出现OOM:Metaspace
运行时常量区

将字节码文件中的常量池存放到运行时常量池中。
方法区的历史变化






浙公网安备 33010602011771号