襄阳市住房城乡建设部网站,苏州商城网站建设,视频类网站备案,开发工具宏怎么使用一、新系统上线如何规划容量#xff1f;
1.套路总结
任何新的业务系统在上线以前都需要去估算服务器配置和JVM的内存参数#xff0c;这个容量与资源规划并不仅仅是系统架构师的随意估算的#xff0c;需要根据系统所在业务场景去估算#xff0c;推断出来一个系统运行模型
1.套路总结
任何新的业务系统在上线以前都需要去估算服务器配置和JVM的内存参数这个容量与资源规划并不仅仅是系统架构师的随意估算的需要根据系统所在业务场景去估算推断出来一个系统运行模型评估JVM性能和GC频率等等指标。以下是我结合大牛经验以及自身实践来总结出来的一个建模步骤
计算业务系统每秒钟创建的对象会佔用多大的内存空间然后计算集群下的每个系统每秒的内存佔用空间对象创建速度设置一个机器配置估算新生代的空间比较不同新生代大小之下多久触发一次 MinorGC。为了避免频繁GC就可以重新估算需要多少机器配置部署多少台机器给JVM多大内存空间新生代多大空间。根据这套配置基本可以推算出整个系统的运行模型每秒创建多少对象1s以后成为垃圾系统运行多久新生代会触发一次GC频率多高。
2.套路实战——以登录系统为例
有些同学看到这些步骤还是发憷说的好像是那么回事一到实际项目中到底怎麽做我还是不知道
光说不练假把式以登录系统为例模拟一下推演过程
假设每天100w次登陆请求登陆峰值在早上预估峰值时期每秒100次登陆请求。假设部署3台服务器每台机器每秒处理30次登陆请求假设一个登陆请求需要处理1秒钟JVM新生代里每秒就要生成30个登陆对象1s之后请求完毕这些对象成为了垃圾。一个登陆请求对象假设20个字段一个对象估算500字节30个登陆佔用大约15kb考虑到RPC和DB操作网络通信、写库、写缓存一顿操作下来可以扩大到20-50倍大约1s产生几百k-1M数据。假设2C4G机器部署分配2G堆内存新生代则只有几百M按照1s1M的垃圾产生速度几百秒就会触发一次MinorGC了。假设4C8G机器部署分配4G堆内存新生代分配2G如此需要几个小时才会触发一次MinorGC。
所以可以粗略的推断出来一个每天100w次请求的登录系统按照4C8G的3实例集群配置分配4G堆内存、2G新生代的JVM可以保障系统的一个正常负载。
基本上把一个新系统的资源评估了出来所以搭建新系统要每个实例需要多少容量多少配置集群配置多少个实例等等这些并不是拍拍脑袋和胸脯就可以决定的下来的。
二、该如何进行垃圾回收器的选择
吞吐量还是响应时间
首先引入两个概念吞吐量和低延迟
吞吐量 CPU在用户应用程序运行的时间 / CPU在用户应用程序运行的时间 CPU垃圾回收的时间
响应时间 平均每次的GC的耗时
通常吞吐优先还是响应优先这个在JVM中是一个两难之选。
堆内存增大gc一次能处理的数量变大吞吐量大但是gc一次的时间会变长导致后面排队的线程等待时间变长相反如果堆内存小gc一次时间短排队等待的线程等待时间变短延迟减少但一次请求的数量变小并不绝对符合。
无法同时兼顾是吞吐优先还是响应优先这是一个需要权衡的问题。
垃圾回收器设计上的考量
JVM在GC时不允许一边垃圾回收一边还创建新对象就像不能一边打扫卫生还在一边扔垃圾。JVM需要一段Stop the world的暂停时间而STW会造成系统短暂停顿不能处理任何请求新生代收集频率高性能优先常用复制算法老年代频次低空间敏感避免复制方式。所有垃圾回收器的涉及目标都是要让GC频率更少时间更短减少GC对系统影响
CMS和G1
目前主流的垃圾回收器配置是新生代采用ParNew老年代采用CMS组合的方式或者是完全采用G1回收器
从未来的趋势来看G1是官方维护和更为推崇的垃圾回收器。 业务系统:
延迟敏感的推荐CMS 大内存服务要求高吞吐的采用G1回收器
CMS垃圾回收器的工作机制
CMS主要是针对老年代的回收器老年代是标记-清除默认会在一次FullGC算法后做整理算法清理内存碎片。
CMSGC描述Stop the world速度1.开始标记初始标记仅标记GCRoots能直接关联到的对象速度很快Yes很快2.并发标记并发标记阶段就是进行GCRoots Tracing的过程No慢3.重新标记重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。Yes很快4.垃圾回收并发清理垃圾对象(标记清除算法)No慢
优点 并发收集、主打“低延时” 。在最耗时的两个阶段都没有发生STW而需要STW的阶段都以很快速度完成。
缺点 1、消耗CPU2、浮动垃圾3、内存碎片
适用场景 重视服务器响应速度要求系统停顿时间最短。
总之 业务系统延迟敏感的推荐CMS 大内存服务要求高吞吐的采用G1回收器
三、如何对各个分区的比例、大小进行规划
一般的思路为:
首先JVM最重要最核心的参数是去评估内存和分配第一步需要指定堆内存的大小这个是系统上线必须要做的-Xms 初始堆大小-Xmx 最大堆大小后台Java服务中一般都指定为系统内存的一半过大会佔用服务器的系统资源过小则无法发挥JVM的最佳性能。
其次需要指定-Xmn新生代的大小这个参数非常关键灵活度很大虽然sun官方推荐为3/8大小但是要根据业务场景来定针对于无状态或者轻状态服务现在最常见的业务系统如Web应用来说一般新生代甚至可以给到堆内存的3/4大小而对于有状态服务常见如IM服务、网关接入层等系统新生代可以按照默认比例1/3来设置。服务有状态则意味著会有更多的本地缓存和会话状态信息常驻内存应为要给老年代设置更大的空间来存放这些对象。
最后是设置-Xss栈内存大小设置单个线程栈大小默认值和JDK版本、系统有关一般默认512~1024kb。一个后台服务如果常驻线程有几百个那麽栈内存这边也会佔用了几百M的大小。
JVM参数描述默认推荐-XmsJava堆内存的大小OS内存64/1OS内存一半-XmxJava堆内存的最大大小OS内存4/1OS内存一半-XmnJava堆内存中的新生代大小扣除新生代剩下的就是老年代的内存大小了默认堆的1/3sun推荐3/8-Xss每个线程的栈内存大小和idk有关sun
对于8G内存一般分配一半的最大内存就可以了,因为机器本上还要占用一定内存一般是分配4G内存给JVM
引入性能压测环节测试同学对登录接口压至1s内60M的对象生成速度采用ParNewCMS的组合回收器
正常的JVM参数配置如下
-Xms3072M -Xmx3072M -Xss1M -XX:MetaspaceSize256M -XX:MaxMetaspaceSize256M -XX:SurvivorRatio8 这样设置可能会由于动态对象年龄判断原则 导致频繁full gc。为啥呢
压测过程中短时间比如20S后Eden区就满了此时再运行的时候对象已经无法分配会触发MinorGC
假设在这次GC后S1装入100M马上过20S又会触发一次MinorGC多出来的100M存活对象S1区的100M已经无法顺利放入到S2区此时就会触发JVM的动态年龄机制将一批100M左右的对象推到老年代保存持续运行一段时间系统可能一个小时候内就会触发一次FullGC。
按照默认8:1:1的比例来分配时, survivor区只有 1G的 10%左右也就是几十到100M
如果 每次minor GC垃圾回收过后进入survivor对象很多并且survivor对象大小很快超过 Survivor 的 50% 那么会触发动态年龄判定规则让部分对象进入老年代.
而一个GC过程中可能部分WEB请求未处理完毕, 几十兆对象进入survivor的概率是非常大的甚至是一定会发生的.
如何解决这个问题呢为了让对象尽可能的在新生代的eden区和survivor区, 尽可能的让survivor区内存多一点,达到200兆左右,
于是我们可以更新下JVM参数设置
-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize256M -XX:MaxMetaspaceSize256M -XX:SurvivorRatio8 说明
‐Xmn2048M ‐XX:SurvivorRatio8
年轻代大小2geden与survivor的比例为8:1:1也就是1.6g:0.2g:0.2gsurvivor达到200m如果几十兆对象到底survivor survivor 也不一定超过 50%
这样可以防止每次垃圾回收过后survivor对象太早超过 50% ,
这样就降低了因为对象动态年龄判断原则导致的对象频繁进入老年代的问题
什么是JVM动态年龄判断规则呢
对象进入老年代的动态年龄判断规则 动态晋升年龄计算阈值Minor GC 时Survivor 中年龄 1 到 N 的对象大小超过 Survivor 的 50% 时则将大于等于年龄 N 的对象放入老年代。
核心的优化策略是是让短期存活的对象尽量都留在survivor里不要进入老年代这样在minor gc的时候这些对象都会被回收不会进到老年代从而导致full gc 。
应该如何去评估新生代内存和分配合适
这里特别说一下JVM最重要最核心的参数是去评估内存和分配
第一步需要指定堆内存的大小这个是系统上线必须要做的-Xms 初始堆大小-Xmx 最大堆大小
后台Java服务中一般都指定为系统内存的一半过大会占用服务器的系统资源过小则无法发挥JVM的最佳性能。
其次需要指定-Xmn新生代的大小这个参数非常关键灵活度很大虽然sun官方推荐为3/8大小但是要根据业务场景来定
针对于无状态或者轻状态服务现在最常见的业务系统如Web应用来说一般新生代甚至可以给到堆内存的3/4大小而对于有状态服务常见如IM服务、网关接入层等系统新生代可以按照默认比例1/3来设置。服务有状态则意味著会有更多的本地缓存和会话状态信息常驻内存应为要给老年代设置更大的空间来存放这些对象。
四、栈内存大小多少比较合适
-Xss栈内存大小设置单个线程栈大小默认值和JDK版本、系统有关一般默认512~1024kb。一个后台服务如果常驻线程有几百个那麽栈内存这边也会佔用了几百M的大小。
五、对象年龄应该为多少才移动到老年代比较合适
假设一次minor gc要间隔二三十秒并且大多数对象一般在几秒内就会变为垃圾
如果对象这么长时间都没被回收比如2分钟没有回收可以认为这些对象是会存活的比较长的对象从而移动到老年代而不是继续一直占用survivor区空间。
所以可以将默认的15岁改小一点比如改为5
那么意味着对象要经过5次minor gc才会进入老年代整个时间也有一两分钟了5*30s 150s和几秒的时间相比对象已经存活了足够长时间了。
所以可以适当调整JVM参数如下
‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:MetaspaceSize256M ‐XX:MaxMetaspaceSize256M ‐XX:SurvivorRatio8 ‐XX:MaxTenuringThreshold5六、多大的对象可以直接到老年代比较合适
对于多大的对象直接进入老年代(参数-XX:PretenureSizeThreshold)一般可以结合自己系统看下有没有什么大对象 生成预估下大对象的大小一般来说设置为1M就差不多了很少有超过1M的大对象
所以可以适当调整JVM参数如下
‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:MetaspaceSize256M ‐XX:MaxMetaspaceSize256M ‐XX:SurvivorRatio8 ‐XX:MaxTenuringThreshold5 ‐XX:PretenureSizeThreshold1M七、垃圾回收器CMS老年代的参数优化
JDK8默认的垃圾回收器是-XX:UseParallelGC(年轻代)和-XX:UseParallelOldGC(老年代)
如果内存较大(超过4个G只是经验 值)还是建议使用G1.
这里是4G以内又是主打“低延时” 的业务系统可以使用下面的组合
ParNewCMS(-XX:UseParNewGC -XX:UseConcMarkSweepGC)新生代的采用ParNew回收器工作流程就是经典复制算法在三块区中进行流转回收只不过采用多线程并行的方式加快了MinorGC速度。
老生代的采用CMS。再去优化老年代参数 比如老年代默认在标记清除以后会做整理还可以在CMS的增加GC频次还是增加GC时长上做些取舍
如下是响应优先的参数调优
XX:CMSInitiatingOccupancyFraction70设定CMS在对内存占用率达到70%的时候开始GC(因为CMS会有浮动垃圾,所以一般都较早启动GC)
XX:UseCMSInitiatinpOccupancyOnly和上面搭配使用否则只生效一次
-XX:AlwaysPreTouch强制操作系统把内存真正分配给IVM而不是用时才分配。
综上只要年轻代参数设置合理老年代CMS的参数设置基本都可以用默认值如下所示
‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:MetaspaceSize256M ‐XX:MaxMetaspaceSize256M ‐XX:SurvivorRatio8 ‐XX:MaxTenuringThreshold5 ‐XX:PretenureSizeThreshold1M ‐XX:UseParNewGC ‐XX:UseConcMarkSweepGC ‐XX:CMSInitiatingOccupancyFraction70 ‐XX:UseCMSInitiatingOccupancyOnly ‐XX:AlwaysPreTouch参数解释
1.‐Xms3072M ‐Xmx3072M 最小最大堆设置为3g最大最小设置为一致防止内存抖动
2.‐Xss1M 线程栈1m
3.‐Xmn2048M ‐XX:SurvivorRatio8 年轻代大小2geden与survivor的比例为8:1:1也就是1.6g:0.2g:0.2g
4.-XX:MaxTenuringThreshold5 年龄为5进入老年代
5.‐XX:PretenureSizeThreshold1M 大于1m的大对象直接在老年代生成
6.‐XX:UseParNewGC ‐XX:UseConcMarkSweepGC 使用ParNewcms垃圾回收器组合
7.‐XX:CMSInitiatingOccupancyFraction70 老年代中对象达到这个比例后触发fullgc
8.‐XX:UseCMSInitiatinpOccupancyOnly 老年代中对象达到这个比例后触发fullgc每次
9.‐XX:AlwaysPreTouch 强制操作系统把内存真正分配给IVM而不是用时才分配。
八、配置OOM时候的内存dump文件和GC日志
额外增加了GC日志打印、OOM自动dump等配置内容帮助进行问题排查
-XX:HeapDumpOnOutOfMemoryError在Out Of MemoryJVM快死掉的时候输出Heap Dump到指定文件。
不然开发很多时候还真不知道怎么重现错误。
路径只指向目录JVM会保持文件名的唯一性叫java_pid${pid}.hprof。
-XX:HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath${LOGDIR}/因为如果指向特定的文件而文件已存在反而不能写入。
输出4G的HeapDump会导致IO性能问题在普通硬盘上会造成20秒以上的硬盘IO跑满
需要注意一下但在容器环境下这个也会影响同一宿主机上的其他容器。
GC的日志的输出也很重要
-Xloggc:/dev/xxx/gc.log
-XX:PrintGCDateStamps
-XX:PrintGCDetailsGC的日志实际上对系统性能影响不大打日志对排查GC问题很重要。
九、一份通用的JVM参数模板
一般来说大企业或者架构师团队都会为项目的业务系统定制一份较为通用的JVM参数模板但是许多小企业和团队可能就疏于这一块的设计如果老板某一天突然让你负责定制一个新系统的JVM参数你上网去搜大量的JVM调优文章或博客结果发现都是零零散散的、不成体系的JVM参数讲解根本下不了手这个时候你就需要一份较为通用的JVM参数模板了不能保证性能最佳但是至少能让JVM这一层是稳定可控的
在这里给大家总结了一份模板
基于4C8G系统的ParNewCMS回收器模板响应优先新生代大小根据业务灵活调整
-Xms4g
-Xmx4g
-Xmn2g
-Xss1m
-XX:SurvivorRatio8
-XX:MaxTenuringThreshold10
-XX:UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction70
-XX:UseCMSInitiatingOccupancyOnly
-XX:AlwaysPreTouch
-XX:HeapDumpOnOutOfMemoryError
-verbose:gc
-XX:PrintGCDetails
-XX:PrintGCDateStamps
-XX:PrintGCTimeStamps
-Xloggc:gc.log如果是GC的吞吐优先推荐使用G1基于8C16G系统的G1回收器模板
G1收集器自身已经有一套预测和调整机制了因此我们首先的选择是相信它
即调整-XX:MaxGCPauseMillisN参数这也符合G1的目的——让GC调优尽量简单
同时也不要自己显式设置新生代的大小用-Xmn或-XX:NewRatio参数
如果人为干预新生代的大小会导致目标时间这个参数失效。
-Xms8g
-Xmx8g
-Xss1m
-XX:UseG1GC
-XX:MaxGCPauseMillis150
-XX:InitiatingHeapOccupancyPercent40
-XX:HeapDumpOnOutOfMemoryError
-verbose:gc
-XX:PrintGCDetails
-XX:PrintGCDateStamps
-XX:PrintGCTimeStamps
-Xloggc:gc.logG1参数描述默认值XX:MaxGCPauseMillisN最大GC停顿时间。柔性目标JVM满足90%不保证100%。200-XX:nitiatingHeapOccupancyPercentn当整个堆的空间使用百分比超过这个值时就会融发MixGC45
针对-XX:MaxGCPauseMillis来说参数的设置带有明显的倾向性调低↓延迟更低但MinorGC频繁MixGC回收老年代区减少增大Full GC的风险。调高↑单次回收更多的对象但系统整体响应时间也会被拉长。
针对InitiatingHeapOccupancyPercent来说调参大小的效果也不一样调低↓更早触发MixGC浪费cpu。调高↑堆积过多代回收region增大FullGC的风险。
调优总结
系统在上线前的综合调优思路
1、业务预估根据预期的并发量、平均每个任务的内存需求大小然后评估需要几台机器来承载每台机器需要什么样的配置。
2、容量预估根据系统的任务处理速度然后合理分配Eden、Surivior区大小老年代的内存大小。
3、回收器选型响应优先的系统建议采用ParNewCMS回收器吞吐优先、多核大内存(heap size≥8G)服务建议采用G1回收器。
4、优化思路让短命对象在MinorGC阶段就被回收同时回收后的存活对象Survivor区域50%可控制保留在新生代长命对象尽早进入老年代不要在新生代来回复制尽量减少Full GC的频率避免FGC系统的影响。
5、到目前为止总结到的调优的过程主要基于上线前的测试验证阶段所以我们尽量在上线之前就将机器的JVM参数设置到最优
JVM调优只是一个手段但并不一定所有问题都可以通过JVM进行调优解决大多数的Java应用不需要进行JVM优化我们可以遵循以下的一些原则
上线之前应先考虑将机器的JVM参数设置到最优减少创建对象的数量代码层面减少使用全局变量和大对象代码层面优先架构调优和代码调优JVM优化是不得已的手段代码、架构层面分析GC情况优化代码比优化JVM参数更好代码层面
通过以上原则我们发现其实最有效的优化手段是架构和代码层面的优化而JVM优化则是最后不得已的手段也可以说是对服务器配置的最后一次“压榨”。