亿级流量系列-JVM调优原理深度剖析、服务线上优化实践

·  阅读 1990
亿级流量系列-JVM调优原理深度剖析、服务线上优化实践

主题:

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

1 为什么要进行JVM调优?

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

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

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

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

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

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

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

2、64位的操作系统 --- 寻址能力 2^64 = 16384PB , 高性能计算机(IBM Z unix 128G 200+)

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

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、引用计数算法 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 串行垃圾回收器

注意:

1、当进行垃圾回收时候,所有的业务线程都必须暂时挂起,以便于垃圾回收进行;

2、safe point就是让用户线程进行挂起,这样jvm线程才可以进行安全的对垃圾进行标记;

3.6 Ps + po

3.7 parNew + CMS

3.8 G1垃圾回收器

4 内存分代模型

问题: 一个大对象来了以后,做什么? eden放得下?--- 否 --- (YGC)--- eden放得下? --- 否 ---- old能下 ----是 --- 可以放入old -----否---- fullgc oom

5 jvm调优实战

5.1 典型调优参数设置

服务器配置: 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 $&

分类:
后端