带你“搞搞”JVM调优原理深度剖析、服务线上优化实践

在这里插入图片描述

  • jvm调优原因 — 为什么要进行jvm调优!(海恩法则,墨菲定律)
  • jvm调优原理 — 垃圾回收算法,如何进行调优
  • jvm调优实战 — 设置jvm调优参数,根据这些参数压力测试
  • jvm调优gc日志,根据日志情况,对服务进行再次调优

1 为什么要进行JVM调优?

思考1: 项目上线后,什么原因使得我们需要进行jvm调优

  • 垃圾太多(java线程,对象占满内存),内存占满了,程序跑不动了!!
  • 垃圾回收线程太多,频繁的回收垃圾(垃圾回收线程本身也会占用资源: 占用内存,cpu资源),导致程序性能下降
  • 回收垃圾频繁导致STW

因此基于以上的原因,程序上线后,必须进行调优,否则程序性能就无法提升;也就是程序上线后,必须设置合理的垃圾回收策略;

思考2: jvm调优的本质是什么??

答案: 回收垃圾,及时回收没有用垃圾对象,及时释放掉内存空间

思考3: 基于服务器环境,jvm堆内存到底应用设置多少内存?

  • 32位的操作系统 — 寻址能力 2^32 = 4GB ,最大的能支持4gb; jvm可以分配 2g+

  • 64位的操作系统 — 寻址能力 2^64 = 16384PB , 高性能计算机(IBM Z unix 128G 200+)
    在这里插入图片描述

jvm堆内存不能设置太大,否则会导致寻址垃圾的时间过长,也就是导致整个程序STW, 也不能设置太小,否则会导致回收垃圾过于频繁;

整理了一些文档笔记。 也还整理了一些面试资料&最新2020收集的一些大厂的面试真题(都整理成文档,小部分截图),有需要的请点这里、这里,暗号:CSDN。

在这里插入图片描述

在这里插入图片描述

整理了一些文档笔记。 也还整理了一些面试资料&最新2020收集的一些大厂的面试真题(都整理成文档,小部分截图),有需要的请点这里、这里,暗号:CSDN。

2 JVM调优原则

  1. gc时间足够小 (堆内存设置要足够小)
    在这里插入图片描述

  2. gc次数足够少 (堆内存设置足够大)---- 垃圾装满了(很长时间才能装满空间),才开始回收垃圾在这里插入图片描述

  3. 发生full gc周期足够长
    1)metaspace 永久代空间设置稍微合理一些,永久代一代扩容,立马发生full gc
    2 ) 老年代设置空间大一些,如果老年代装不满的话,永远不会发生full gc
    3 ) 尽量让垃圾再年轻代被回收掉
    4)尽量少一些大对象

3 JVM调优原理-GC

3.1 什么是垃圾?

在这里插入图片描述
在内存中没有被引用的对象,这些对象就是垃圾;在堆内存中,一个对象引用没了,那么这个对象就必须要被回收;

3.2 怎么找垃圾?

JVM中有2种垃圾寻找算法(找垃圾的方法)

  • 引用计数算法
  • 根可达算法

1)引用计数算法
在这里插入图片描述
引用计数算法判断对象是垃圾的方法就是,当计数为0的时候,就判断此对象为垃圾; 存在问题:不能解决循环引用计数的问题;
在这里插入图片描述
以上这个对象相互引用,导致引用计数始终不为0,因此无法判断其是垃圾,但是这几个对象都没有被外部对象引用,因此他们就是垃圾;

2)、根可达算法

在这里插入图片描述
根可达算法是目前jvm使用主流的垃圾回收算法;oracle公司hotspot 都是使用这个算法;

3.3 如何清除垃圾?

JVM提供了3种方法(算法)

1、Mark-sweep — 标记清除算法
2、copying – 拷贝
3、mark-compact 标记压缩算法

1)Mark-sweep — 标记清除算法

在这里插入图片描述
1、从内存空间中寻找垃圾,对找到的垃圾进行标记
2、对标记的垃圾进行清除即可

  • 优点: 简单,高效
  • 缺点: 存在很多不连续的内存空间,内存空间碎片化;导致寻址的效率下降,性能下降

2)、copying – 拷贝
在这里插入图片描述
1、选择存活对象
2、把存活对象拷贝另一半空间中,这个是连续的内存空间
3、把存活对象全部拷贝完毕后,直接清除掉上面那一半空间即可完成垃圾回收;

优点: 简单,内存空间是连续的,不用担心内存空间碎片化
缺点: 内存空间的浪费

3)mark-compact 标记压缩算法
在这里插入图片描述
1、标记垃圾,不清除垃圾
2、再次扫描,并向一端滑动(copy)存活对象(没有标记的对象)
3、回收另一端内存空间中垃圾

3.4 可用垃圾回收器

Java语言有一个特性,自己有自己的垃圾回收器;而且有很多个垃圾回收器;因此就需要根据不同的场景,选择不同的垃圾回收器;
在这里插入图片描述
JVM提供了10种垃圾回收器;如此多的垃圾回收器,在项目中到底使用哪个组合的垃圾回收器呢??

1)serial(年轻代垃圾回收) + serialOld(回收老年代的垃圾) : 串行化的回收器,因此在当下多核心cpu的时候,不太适合,只适合单核cpu的垃圾回收;

2)parNew + CMS : 并行;并发 垃圾回收器;此组合在响应时间优先垃圾回收器组合

3)Parallel Scavenge + Parallel Old (简称:ps+po): 并发垃圾回收器,是jdk默认的垃圾回收器组合

4)g1 垃圾回收器 (逻辑上分代),把年轻代和老年代合二为一的方式进行垃圾回收;

3.5 串行垃圾回收器

4 内存分代模型

在这里插入图片描述
问题: 一个大对象来了以后,做什么? eden放得下?

— 否 — (YGC)— eden放得下? — 否 ---- old能下 ----是 — 可以放入old -----否---- fullgc oom

5 JVM调优实战

服务器配置: 4cpu,8GB内存 ---- jvm调优实际上是设置一个合理大小的jvm堆内存(既不能太大,也不能太小)

-Xmx3550m  设置jvm堆内存最大值  (经验值设置: 根据压力测试,根据线上程序运行效果情况)
-Xms3550m  设置jvm堆内存初始化大小,一般情况下必须设置此值和最大的最大的堆内存空间保持一致,防止内存抖动,消耗性能
-Xmn2g 设置年轻代占用的空间大小
-Xss256k 设置线程堆栈的大小;jdk5.0以后默认线程堆栈大小为1MB; 在相同的内存情况下,减小堆栈大小,可以使得操作系统创建更多的业务线程;

jvm堆内存设置:

nohup java -Xmx3550m -Xms3550m -Xmn2g -Xss256k -jar jshop-web-1.0-SNAPSHOT.jar --spring.addition-location=application.yaml > jshop.log 2>&1 $&

TPS性能曲线:
在这里插入图片描述

5.2 分析gc日志

如果需要分析gc日志,就必须使得服务gc输入gc详情到log日志文件中,然后使用相应gc日志分析工具来对日志进行分析即可;

把gc详情输出到一个gc.log日志文件中,便于gc分析

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC -Xloggc:gc.log

Throughput: 业务线程执行时间 / (gc时间+业务线程时间)
在这里插入图片描述
分析gc日志,发现,一开始就发生了3次fullgc,很明显jvm优化参数的设置是有问题的;
在这里插入图片描述
查看fullgc发生问题原因: jstat -gcutil pid
在这里插入图片描述
Metaspace持久代:
初始化分配大小20m , 当metaspace被占满后,必须对持久代进行扩容,如果metaspace每进行一次扩容,fullgc就需要执行一次;(fullgc回收整个堆空间,非常占用时间)

调整gc配置: 修改永久代空间初始化大小:

nohup java -Xmx3550m -Xms3550m -Xmn2g -Xss256k -XX:MetaspaceSize=256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -jar jshop-web-1.0-SNAPSHOT.jar --spring.addition-location=application.yaml > jshop.log 2>&1 $&

经过调优后,fullgc现象已经消失了:
在这里插入图片描述

5.3 Young&Old比例

年轻代和老年代比例:1:2 参数:-XX:NewRetio = 4 , 表示年轻代(eden,s0,s1)和老年代所占比值为1:4

1) -XX:NewRetio = 4
在这里插入图片描述年轻代分配的内存大小变小了,这样YGC次数变多了,虽然fullgc不发生了,但是YGC花费的时间更多了!

2) -XX:NewRetio = 2 YGC发生的次数必然会减少;因为eden区域的大小变大了,因此YGC就会变少;
在这里插入图片描述

5.4 Eden&S0S1

为了进一步减少YGC, 可以设置 enden ,s 区域的比值大小; 设置方式: -XX:SurvivorRatio=8

1) 设置比值:8:1:1

在这里插入图片描述
2) Xmn2g 8:1:1

nohup java -Xmx3550m -Xms3550m -Xmn2g -XX:SurvivorRatio=8 -Xss256k -XX:MetaspaceSize=256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -jar jshop-web-1.0-SNAPSHOT.jar --spring.addition-location=application.yaml > jshop.log 2>&1 $&

根据gc调优,垃圾回收次数,时间,吞吐量都是一个比较优的一个配置;
在这里插入图片描述

5.5 吞吐量优先

使用并行的垃圾回收器,可以充分利用多核心cpu来帮助进行垃圾回收;这样的gc方式,就叫做吞吐量优先的调优方式
垃圾回收器组合: ps(parallel scavenge) + po (parallel old)
此垃圾回收器是Jdk1.8 默认的垃圾回收器组合;

nohup java -Xmx3550m -Xms3550m -Xmn2g -XX:SurvivorRatio=8 -Xss256k -XX:+UseParallelGC -XX:UseParallelOldGC -XX:MetaspaceSize=256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -jar jshop-web-1.0-SNAPSHOT.jar --spring.addition-location=application.yaml > jshop.log 2>&1 $&

5.6 响应时间优先

使用cms垃圾回收器,就是一个响应时间优先的组合; cms垃圾回收器(垃圾回收和业务线程交叉执行,不会让业务线程进行停顿stw)尽可能的减少stw的时间,因此使用cms垃圾回收器组合,是响应时间优先组合

nohup java -Xmx3550m -Xms3550m -Xmn2g -XX:SurvivorRatio=8 -Xss256k -XX:+UseParNewGC -XX:UseConcMarkSweepGC -XX:MetaspaceSize=256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -jar jshop-web-1.0-SNAPSHOT.jar --spring.addition-location=application.yaml > jshop.log 2>&1 $&

可以发现,cms垃圾回收器时间变长;

5.7 g1

配置方式如下所示:

nohup java -Xmx3550m -Xms3550m -Xmn2g -XX:SurvivorRatio=8 -Xss256k -XX:+UseG1GC -XX:MetaspaceSize=256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -jar jshop-web-1.0-SNAPSHOT.jar --spring.addition-location=application.yaml > jshop.log 2>&1 $&

在这里插入图片描述

点关注,不迷路!如果本文对你有帮助的话不要忘记点赞支持哦!

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41770757/article/details/109607679