【精华】搞定JVM调优学习
创始人
2024-05-10 03:52:26
0

JVM 介绍

1. 什么是 JVM

JVM 是 Java Virtual Machine(Java 虚拟机)的缩写。一台执行 Java 程序的机器。

2 .JAVA 语言的执行原理

计算机语言
计算机能够直接执行的指令。这种指令和系统及硬件有关。

计算机高级语言
在遵循语法的前提下,写一个文本文件,之后利用某种方式,把文本转换为计算机指令
执行。

A. 编译型语言(C 语言):文本文件(.c) --> 编译器 --> 可执行文件(.exe) -->
执行机器指令。特点:运行速度快,但不能跨平台

B. 解释型语言(JavaScript):文本文件 --> 解释器 --> 翻译成机器指令并执
行。特点:运行速度较慢,但能跨平台

JAVA 语言:先编译,后解释执行
文本文件(java) --> 编译器 --> class 文件(虚拟指令) --> JAVA 虚拟机(JVM)–> 解释为指令执行。

3.JDK+JRE+JVM

(1)JDK(IAVA开发环境)=:JRE+工具(编译器、调试器、其他工具)+类库
编译器:将JAVA文件编译为JVM能够看懂的文件(class文件)

(2)JRE(JAVA运行环境):JVM+JAVA解释器
Java解释器:将虚拟指令解释为机器指令执行。

(3)JVM(JAVA虚拟机)

在这里插入图片描述

4. JAVA字节码文件结构

在这里插入图片描述在这里插入图片描述

打开class文件查看结构
在这里插入图片描述在class字节码最前面都会有cafe babe
在这里插入图片描述
什么是 u2,u4?
u2:代表数据占两个字节
u4:代表数据占四个字节
在这里插入图片描述JDK 编译对应的版本号

  • JDK7 --> 51
  • JDK8 --> 52
  • JDK9 --> 53
  • JDK15 --> 59

结论

编译的本质就是将 java 源文件转为 JVM 能够认识的 16 进制 class 文件格式

三、 类加载机制

1 类加载过程

1.1 装载

(1)获取类的全限定类名,把 class 文件转为二进制流
(2)将二进制流中类的描述信息存入方法区中。如:创建时间、版本等… (3)将 java.lang.Class 对象存入堆中。

1.2 链接

(1)验证:验证被加载类的正确性:如文件的格式,元数据等。
(2)准备:在方法区中为静态变量分配空间,并设置初始值。

(3)解析:把类的符号引用转为直接引用。
符号引用:class 文件定义的内容
直接引用:JAVA 进程中真实的地址

1.3 初始化

为类的静态变量设置默认值、执行静态代码块。

2 类加载器

2.1 分类

不同的类加载器加载不同的类:

启动类加载器(Bootstrap classLoader): 主要负责加载 JAVA 中的 一些核心类库,主要是位于/lib/rt.jar 中。

拓展类加载器(Extension classLoader):主要加载 JAVA 中的一些拓展类,位于/lib/ext 中,是启动类加载器的子类。

应用类加载器(System classLoader): 主要用于加载 CLASSPATH 路径下我们自己写的类,是拓展类加载器的子类

2.2 双亲委派模型

如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给
父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器。如果父类加载器可以完成类加载任务,就成功返回。
倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载。
在这里插入图片描述

四、 JVM 内存模型

1 什么是 JVM 内存模型

JVM 需要使用计算机的内存,Java 程序运行中所处理的对象或者算法都会使用 JVM 的内
存空间,JVM 将内存区划分为 5 块,这样的结构称之为 JVM 内存模型。

2 JVM 为什么进行内存区域划分

随着对象数量的增加,JVM 内存使用率也在增加,如果 JVM 内存使用率达到 100%,
则无法继续运行程序。为了让 JVM 内存可以被重复使用,我们需要进行垃圾回收。为了提
高垃圾回收的效率,JVM 将内存区域进行了划分。

3 JVM 内存划分

JVM 按照线程是否共享将内存首先分成两大类
线程独享区
只有当前线程能访问数据的区域,线程之间不能共享
线程独享区随线程的创建而创建,随线程的销毁而被回收
线程共享区
所有线程都可以访问的区域,
当线程被销毁的时候,共享区的数据不会立即回收,需要等待达到垃圾回收的阈(yu)
值之后才会进行回收。
在这里插入图片描述

4 程序计数器

程序计数器会记录当前线程要执行指令的内存地址,只占用一小部分内存区域,只记录一个
地址,所以我们认为程序计数器是不会出现内存溢出问题的分区。

5 本地方法栈

Java 中有些代码的实现是依赖于其他非 Java 语言的(C++),本地方法栈存储的是维护
非 Java 语句执行过程中产生的数据,一般我们认为本地方法栈不会出现内存的问题。

6 虚拟机栈

6.1 虚拟机栈的作用

存放当前线程中所声明的变量,包括基本数据类型的数据和引用数据类型的引用。
基本数据类型和引用数据类型划分的标准:

  • 基本数据类型:
    变量在声明的时候,能够确认占用内存的大小。
  • 引用数据类型:
    变量在声明的时候,不能确认占用内存的大小。
    引用数据类型将值的引用存放到虚拟机栈中,而对象存放在堆内存中,引用数据类型占用 4
    个字节存放地址

6.2 栈帧

每一个线程都会对应一个虚拟机栈,线程中的每个方法都会创建一个栈帧,存放本次方法执
行过程中所需要的所有数据。
如果我们一个线程中有多个方法的嵌套调用,虚拟机栈会对栈帧进行压栈和出栈操作。正在
执行的方法一定在栈顶,我们只能获取栈顶的栈帧,栈帧在虚拟机栈中先进后出。

6.3 栈帧的数据结构

局部变量
存放当前方法的局部变量,基本数据类型存值,引用数据类型存堆内存地址。
操作数栈
对方法中的变量提供计算的区域。
常量数据的引用
常量数据会存放到方法区的常量池中,不管是基本数据类型还是引用数据类型都会存放常量
池的地址
方法返回值的地址
方法返回数据会存到计算机内存的寄存器中。

6.4 虚拟机栈溢出异常

由于栈帧调用的深度太深,会出现虚拟机栈溢出异常(SOF 异常)。一般手动方法的调用
是不会出现这个异常的,如果出现这个异常 ,99%是由于递归。
可以通过修改虚拟机栈的内存大小设置栈帧的最大深度,指令为:

-Xss 虚拟机栈内存大小

一般栈帧深度达到 3000~5000 即可
太小:虚拟机栈容易溢出。
太大:每个线程占据的内存过大,影响线程数量。

7 方法区

在 java8 之后,我们把方法区称之为元空间(MetaSpace),方法区在逻辑上属于堆
的一部分,但一些具体机制和堆有所区别,如:一些 JVM 的方法区是可以不进行垃圾回收
的,关闭 JVM 时才会释放方法区内存。所以方法区还有一个别名叫非堆,目的是和堆分开。
方法区会存储类信息、静态变量、常量(JDK8 之后不存放字符串常量)、本地机器指
令。
如果加载大量 class 文件,也会造成方法区内存溢出,如一个 tomcat 运行 20~30 个
项目。

五、 JVM 执行引擎

1 什么是 JVM 执行引擎

执行引擎是 Java 虚拟机核心的组成部分之一。JVM 的将字节码装载到内存,但字节码
并不能够直接运行在操作系统之上。为了执行内存中的字节码文件指令,执行引擎
(Execution Engine)就要将字节码指令解释/编译为对应平台上的本地机器指令。

执行引擎的翻译过程有两种:
1、通过解释器将字节码文件转为机器指令执行;
2、使用即时编译器(JIT)将字节码文件的二进制流编译成机器指令执行。

目前市面的主流 JVM 采用解释器与即时编译器并存的架构。在 Java 虚拟机运行时,
释器
即时编译器相互协作,取长补短。在今天,Java 程序的运行性能早已脱胎换骨,已经
达到了可以和 C/C++程序一较高下的地步。

2 解释器与即时编译器

解释器每次解释都会将字节码文件解释为机器指令。整体效率较低,但当程序启动后, 解释器可以马上发挥作用,省去编译的时间,立即执行。

即时编译器则会将字节码文件编译为机器指令,存在方法区中,编译完成后直接执行本
地机器指令即可。编译器把代码编译成本地代码需要一定的执行时间,但编译为本地代码后
执行效率高。

当 Java 虚拟器启动时,解释器首先发挥作用,不必等待即时编译器全部编译完成后再
执行。随着时间的推移,编译器把越来越多的代码编译成本地代码,此时运行本地机器指令,
获得更高的执行效率。

六、 堆内存模型

JVM 将对象存放在堆内存中,堆内存所需要的空间是比较大的。我们对于 JVM 的调优
也主要是针对堆内存的调优,比如分配堆内存的空间,那么我们如何能确定堆内存需要分配
多少空间呢?我们需要大概计算每个对象所占的空间大小

1 JAVA 对象内存布局

在这里插入图片描述JAVA 对象在内存中主要有以下几部分:

对象头
MarkWord:一系列标记位(哈希码、分代年龄、锁状态标记等),在 64 位系统中占
8 字节。
ClassPoint:对象对应的类信息的内存地址,在 64 位系统中占 8 字节。
Length:数组对象特有,表示数组长度,占 4 字节。

实例数据
包含了对象的所有成员变量,大小由变量类型决定。
byte、boolean:1 字节
short:2 字节
char:2~3 字节
int、float:4 字节
long、double、引用数据类型:8 字节

对其填充
将对象大小填充为 8 字节的整数倍

2 JVM 内存溢出和垃圾回收机制

为什么要进行垃圾回收:
如果对象只创建不回收,会造成堆内存溢出(OOM)异常。
为什么要进行堆内存分区:

  • 提高搜索垃圾的效率。
  • 垃圾回收后可以更好的利用内存空间,存放大对象。
  • 尽可能减少 GC 次数。

3 JVM 堆内存的划分

老年代:

对象会优先分配到新生代内存中,每次 GC 后没有回收的对象年龄加 1,年龄到15 还没有被回收,对象会存放到老年代内存中;如果对象较大,超过新生代内存的一半,对象也会存放到老年代区域。

新生代:

为了减少young区垃圾回收后的空间碎片,新生代又分为Eden区和两个Survivor
区,且始终有一个 Suvivor 区保持闲置。对象会先存放到 Eden 区当中,Eden 区空间
满了之后会进行 young 区的垃圾回收,之后将 young 区所有存活的对象复制到闲置
的 Suvivor 区中,并清空 Eden 区和正在使用的 Survivor 区。

4 YoungGC 和 OldGC

YoungGC

新生代区域的垃圾回收称之为 YoungGC,也叫 MinorGC,Eden 区满后会触发YoungGC

OldGC

老年代区域的垃圾回收称之为 OldGC,也叫 MajorGC,OldGC 非常浪费性能,
所以我们的 JVM 调优要尽可能减少 OldGC 的次数, OldGC 往往伴随着 YoungGC。
YoungGC+OldGC = FullGC

问题:
- 1.Survivor 区空间并不大,如果满了怎么办?
(1)一般情况下 GC 会回收 95%的对象,且超过 15 次 GC 的对象会存放到 old
去,所以 Survivor 区不容易满。
(2)如果 Survivor 区满了,会触发担保机制,提前将对象存入 Old 区。

- 2.为什么需要 Survivor 区?
为了减少垃圾回收带来的空间碎片,空间碎片过多会频繁触发 YoungGC。

- 3.为什么需要两块 Survivor 区?
为了减少 Survivor 区的空间碎片。

七、 垃圾回收机制

堆内存模型设计是为了提高垃圾回收的效率,那么如何判断一个对象是垃圾?

1 如何判断一个对象是垃圾

引用计数法:

如果要操作对象,必须通过引用来进行。如果一个对象没有任何引用与之关联,则说明
该对象基本不太可能在其他地方被使用到。那么这个对象就成为可被回收的对象了。这种方
式实现简单,效率较高,但是它无法解决循环引用的问题,因此在 Java 中并没有采用这种
方式(Python 采用的是引用计数法)。

可达性分析:

以一个 GC Root 对象作为起点进行搜索,如果在 GC Roots 和对象之间没有可达路径,
则称该对象是不可达的。

GC ROOT 对象:

  • 栈帧中的本地变量表中引用的对象。
  • 方法区中静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中引用的对象。

2 垃圾回收算法

(1)标记——清除算法:效率较低,有空间碎片。Old 区使用的算法
在这里插入图片描述(2)复制算法:空间碎片少,但会浪费空间。存活对象较少才会使用的算法。young
区使用的算法
在这里插入图片描述
(3)标记——整理算法:空间碎片少,效率较低。Old 区使用的算法。

在这里插入图片描述

3 垃圾收集器的评判标准

垃圾收集器是对垃圾回收算法的实现,JVM 中提供了很多垃圾收集器,我们如何评判一个垃圾收集器的好坏呢?

垃圾收集器的执行效率 = 吞吐量 / 停顿时间
吞吐量 = 用户代码执行时间/(用户代码执行时间+停顿时间)

4 垃圾收集器的类型

  • 串行收集器:
    只有一个垃圾回收线程,在垃圾回收时暂停用户代码线程,如 Serial 和 Serial Old收集器。
    在这里插入图片描述
  • 并行收集器

吞吐量优先,多个垃圾收集器线程共同工作, 尽快完成垃圾收集。如 ParNew,Parallel Scanvenge, Parallel Old 收集器
在这里插入图片描述

  • 并发收集器

停顿时间优先,用户线程和垃圾回收线程一同工作,用户代码线程也会完全停止一小段时间,如CMS,G1 收集器。

在这里插入图片描述

5 CMS 收集器

CMS(concurrent mark sweep,并发标记扫描)收集器是并发收集器,是基于标记 ——清理的算法进行垃圾回收,用于 OldGC。

  • 优点:并发收集、低停顿
  • 缺点:会产生大量空间碎片,停顿时间虽然短但是不可控。

问:CMS 收集器为什么不进行并发的初始标记?
因为初始标记速度很快,不值得多开线程,开线程也是需要耗费资源的。

6 G1 收集器

G1(garbage first,垃圾优先)收集器是并发收集器,从 JDK1.7 开始支持,能进行OldGC 和 YoungGC。Old 区采用标记整理算法,Young 区采用复制算法。

G1 收集器没有固定的 Old、Young、Eden、Survivor 区,而是将内存分为一个个大小相等的 Region(格子,1Mb~32Mb)。每次垃圾回收后,Region 的用途可以发生改变,提高了内存的灵活性和利用率。
在这里插入图片描述G1 收集器可以根据开发者设置的参数,停顿时间的期望值,优先筛选回收存活的对象
比较少,垃圾对象比较大的区域 Region,可以把更多空余的空间释放出来。
在这里插入图片描述

7 ZGC 收集器

ZGC 从 JDK11 开始支持,目前还是一个实验性版本,原理类似 G1。是目前收集效率
最高的垃圾收集器,平均暂停时间为 0.05 毫秒。

8 如何选择垃圾收集器?

  • 优先让服务器自己来选择
  • 如果内存小于 100M,使用串行收集器
  • 如果服务器是单核,并且没有停顿时间要求,使用串行收集器
  • 如果允许停顿时间超过 1 秒,选择并行收集器
  • 如果停顿时间不能超过 1 秒,使用并发收集器

八、 JVM 参数设置

1 JVM 参数设置方式

Intellij idea:在运行设置的 VM Option 中设置。
tomcat:进入 Tomcat 的 bin 目录下,打开文件 catalina.bat/catalina.sh,修改如下
参数

set "JAVA_OPTS=参数"

2 JVM 参数类型

(1)标准参数:不随 jdk 版本的变化而变化的参数,如:-version
(2)-X 参数:不能保证所有的 JVM 都支持。
如:-Xcomp:使用即时编译器执行字节码文件
-Xint:使用解释器执行字节码文件
-Xmixed:混合模式,先使用解释器,即时编译器编译好后执行机器指令。
(3)-XX 参数:不能保证所有的 JVM 都支持。
A. Boolean 类型参数:
-XX:+UseG1GC:使用 G1 收集器
-XX:-UseG1GC:不使用 G1 收集器
B. Key-Value 类型参数:
-XX:MaxTenuringThreshold=15:对象年龄达到 15 就会进入老年代

3 常用参数

在这里插入图片描述

九、 JVM 常用命令和常用工具

1 JVM 常用命令

jps:查看当前执行的所有 JAVA 进程
jinfo:实时查看 JVM 参数
jinfo -flag InitialHeapSize PID:JAVA 进程堆内存大小
jinfo -flag UseG1GC PID:JAVA 进程是否使用 G1GC
jinfo -flag UseParallelGC PID:JAVA 进程是否使用 ParallelGC
jstat:虚拟机性能信息
jstat -class PID 1000:每秒查看一次虚拟机中类加载信息
jmap:打印快照
jmap -heap PID:查看堆存储快照
jmap -dump:format=b,file=heap.hprof PID:在出现内存溢出异常时,将堆内存的信息下载到文件中

十一、 JVM 调优思路

1 高并发环境下如何配置堆和垃圾回收器?在这里插入图片描述

2 生产环境 JVM 问题的排查

在这里插入图片描述

3 常见面试题补充

内存泄漏和内存溢出是一样的概念吗?

不一样,内存泄漏指不再使用的对象无法得到及时的回收,持续占用内存空间,造成内存空间的浪费。内存溢出指程序运行要用到的内存大于能提供的最大内存。内存无法内存泄漏很容易导致内存溢出,内存溢出不一定是内存泄漏导致的。

GC Root 不可达的对象一定会被回收吗?

不一定,不可达的对象也不是非死不可的,G1 收集器最终就会筛选要回收的对象。

方法区中的类会被回收吗?

有可能。需要满足以下三个条件:
(1)该类的所有的实例都已经被回收了。
(2)该类的 ClassLoader 已经被回收了。(3)java.lang.class 对象没有任何地方使用。

CMS 和 G1 的区别
cms 只能适用于老年代,g1 可以用于老年代和新生代。
cms 使用了标记——清除算法,会产生大量碎片,g1 使用标记——整理算法,减
少了碎片的产生。
g1 更加灵活,它将内存分为了一块块 region,并且停顿时间可控。

JVM内存区域

请添加图片描述

JVM执行引擎
请添加图片描述

堆内存模型
请添加图片描述

可达性分析
请添加图片描述

类加载过程
请添加图片描述

栈帧
请添加图片描述

栈帧的结构
请添加图片描述

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...