JAVA JVM运行时数据区详解

一、前言

这是JVM系列文章的第三篇,这篇文章将对整个JVM运行时数据区和GC垃圾回收详细的介绍。这部分也算是JVM的核心内容了。

二、运行时数据区整体概架构

JAVA JVM运行时数据区详解_第1张图片

以下是自己的一句话总结:

分为线程私有和线程共享的两大类,其中程序计数器、虚拟机栈、本地方法栈是属于线程私有的,堆内存及方法区内存是线程共享的。程序计数器主要是记录字节码指令,CPU上下文切换线程,从一个线程切换到另一个线程,需要知道线程执行到哪一步,所以记录这个指令就是很有必要的,程序计数器无OOM和GC的发生。虚拟机栈里面是一个个栈帧,每一个栈帧对应着每一个方法,栈帧又是由局部变量表、操作数栈、方法返回值地址、动态链接组成。虚拟机栈可能会发生栈溢出异常,即starkoverflow本地方法栈是存放本地方法相关的东西;堆是一块很大的空间,整体分为2大块,新生代和老年代,新生代又分了Eden区、S0区、S1区,垃圾回收主要发生在新生代,每一个区对应不同的垃圾回收算法;方法区保存的是一些常量、类的基本信息等,方法区对应的实现在JDK7中是永久代,在JDK8中是元空间。

三、程序计数器

用来储存指向下一条指令的地址,是线程私有的,生命周期和线程的生命周期一致。

JAVA JVM运行时数据区详解_第2张图片

JAVA JVM运行时数据区详解_第3张图片

JAVA JVM运行时数据区详解_第4张图片

JAVA JVM运行时数据区详解_第5张图片

四、虚拟机栈

虚拟机栈是线程私有的,内部保存一个个栈帧,每一个栈帧对应一个Java方法的调用,生命周期和线程的生命周期保持一致。先来看看栈的特点。

JAVA JVM运行时数据区详解_第6张图片

1、栈的特点

栈是运行时的单位,而堆是存储的单位。栈的特点是先进后出,后进先出。

JAVA JVM运行时数据区详解_第7张图片

JAVA JVM运行时数据区详解_第8张图片

JAVA JVM运行时数据区详解_第9张图片

JAVA JVM运行时数据区详解_第10张图片

可以通过参数-Xss来设置栈空间大小

2、栈帧的内部结构

JAVA JVM运行时数据区详解_第11张图片

3、局部变量表

是一个数字数组,主要用于存储方法参数和定义在方法内的局部变量,这些数据类型包括各类基本数据类型,对象引用等,所需的容量大小是在编译期确定下来的,在方法运行期间是不会改变局部变量表大小的。

关于Slot的理解:

JAVA JVM运行时数据区详解_第12张图片

静态变量和局部变量的区别:

JAVA JVM运行时数据区详解_第13张图片

总结:

在栈帧中,与性能关系最为密切的就是局部变量表,在方法执行时,虚拟机使用局部变量表完成完成方法的传递,局部变量表中的数据也是可达性分析中的GC Root,如果一个对象在局部变量表中还有引用,那么根绝可达性分析算法,这个变量就不属于垃圾对象,是不会被GC回收的。

4、操作数栈

操作数栈是栈中栈,也可称为表达式栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈和出栈。主要用于保存计算过程的中间结果。操作数栈,可以看成是临时寄存器,计算过程中变量的临时保存

JAVA JVM运行时数据区详解_第14张图片

JAVA JVM运行时数据区详解_第15张图片

JAVA JVM运行时数据区详解_第16张图片

JAVA JVM运行时数据区详解_第17张图片

JAVA JVM运行时数据区详解_第18张图片

5、动态链接

JAVA JVM运行时数据区详解_第19张图片

JAVA JVM运行时数据区详解_第20张图片

方法重写的本质

JAVA JVM运行时数据区详解_第21张图片

6、方法返回地址

存放调用该方法的PC寄存器的值

JAVA JVM运行时数据区详解_第22张图片

五、本地方法栈

管理本地native本地方法,是线程私有的,所谓的本地方法,其实就是一些非Java语言写的代码,这部分代码甚至可以和操作系统CPU进行打交道。

六、堆

堆是内存管理的核心区域,是线程共享的,属于JVM级别,也就是一个JVM实例就会有一个堆空间,注意的是虽然堆整体上是线程共享的,但是在内部有一小块空间是线程私有的缓存区TLAB。

几乎所有的对象实例都是在堆中,堆是GC垃圾回收的重点区域。堆整体可以分为新生代和老年代,新生代又分为Eden区和S0和S1区。

JAVA JVM运行时数据区详解_第23张图片

新生代和老年代的比例是1:2,Eden区和s0,s1区所占空间比例是8:1:1

1、设置堆大小的参数

-Xms:用于表示堆区的起始内存,默认情况下,占物理内存大小的64分之一。

-Xmx用于表示堆区的最大内存,默认情况下,占物理内存的四分之一。

通常起始内存和最大内存两个参数设置成一样,目的是为了GC清理完堆区内存后不需要重新分隔
计算堆区的大小,从而提高性能。
查看设置的参数:
方式一:jps(查看进程)  
            jstat -gc 进程id
方式二:-xx:+printGCDetails

2、对象分配过程

JAVA JVM运行时数据区详解_第24张图片

这里s0和s1谁是空的谁就是to,年龄计数器阈值是15,YGC是在Eden区满的时候会触发,s0和s1满的时候不会触发YGC,YGC会将s区以及伊甸园区一起GC

关于垃圾回收,频繁在新生区收集,很少在养老区收集,几乎不在永久区/元空间收集。

JAVA JVM运行时数据区详解_第25张图片

Visualvm是JVM常用调优工具,在JDK的bin下就可以打开

3、堆中的GC

JAVA JVM运行时数据区详解_第26张图片

年轻代(Minor GC)触发机制

JAVA JVM运行时数据区详解_第27张图片

老年代GC(Major GC/Full GC)触发机制

Full GC 触发机制

JAVA JVM运行时数据区详解_第28张图片

4、内存分配策略

JAVA JVM运行时数据区详解_第29张图片

5、什么是TLAB

JAVA JVM运行时数据区详解_第30张图片

JAVA JVM运行时数据区详解_第31张图片

TLAB表明堆不一定是共享的。

6、堆是分配对象存储的唯一选择吗?

如果经过逃逸分析,一个对象并没有逃逸出方法的话,那么就有可能被优化成栈上分配。

逃逸分析手段:

JAVA JVM运行时数据区详解_第32张图片

JAVA JVM运行时数据区详解_第33张图片

注意:JDK6U23版本后,HotSpot默认已经开启逃逸分析。所以我们得出一个结论,开发中能使用局部变量的,就不要使用在方法外定义。JDK7后字符串常量池和静态变量存储在堆中

七、方法区

方法区可以看做是一块独立于堆的内存空间,是线程共享的,主要存储类信息、运行时常量池等,也会发生OOM,JDK8前成为永久代,JDK8成为元空间。(元空间和永久代最大的区别是,元空间不再使用JVM内存,而是使用了本地内存技术)

1、方法区概述

JAVA JVM运行时数据区详解_第34张图片

JAVA JVM运行时数据区详解_第35张图片

JAVA JVM运行时数据区详解_第36张图片

JAVA JVM运行时数据区详解_第37张图片

2、设置方法区内存大小

JAVA JVM运行时数据区详解_第38张图片

JAVA JVM运行时数据区详解_第39张图片

3、如何解决OOM问题?

JAVA JVM运行时数据区详解_第40张图片

4、方法区存储什么

JAVA JVM运行时数据区详解_第41张图片

JAVA JVM运行时数据区详解_第42张图片

JAVA JVM运行时数据区详解_第43张图片

JAVA JVM运行时数据区详解_第44张图片

JAVA JVM运行时数据区详解_第45张图片

5、方法区的演进细节

JAVA JVM运行时数据区详解_第46张图片

JAVA JVM运行时数据区详解_第47张图片

6、方法区的GC

JAVA JVM运行时数据区详解_第48张图片

JAVA JVM运行时数据区详解_第49张图片

JAVA JVM运行时数据区详解_第50张图片

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

你可能感兴趣的