| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 开发测试 -> JVM性能调优实战 -> 正文阅读 |
|
[开发测试]JVM性能调优实战 |
JVM调优调什么JVM 调优是一个系统而又复杂的过程,但我们知道,在大多数情况下,我们基本不用去调整 JVM 内存分配,因为一些初始化的参数已经可以保证应用服务正常稳定地工作了。而且一般情况下,就算出现了,也是架构师级别的去处理。 实际上,JVM调优,调的是稳定,并不能带给你性能的大幅提升。服务稳定的重要性就不用多说了,保证服务的稳定,gc永远会是Java程序员需要考虑的不稳定因素之一。复杂和高并发下的服务,必须保证每次gc不会出现性能下降,各种性能指标不会出现波动。因此我们通过压测的方式来看一下对JVM的性能是否造成影响。而压测主要就是看吞吐量和响应时间。 JDK1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)。Parallel就是关注吞吐量的垃圾收集器,高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务。通过命令 所谓吞吐量就是 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值,即:
压测工具 ABAb(ApacheBench) 测试工具是 Apache 提供的一款测试工具,具有简单易上手的特点,在测试 Web 服务时非常实用。ab 一般都是在 Linux 上用。安装非常简单,只需要在 Linux 系统中输入命令安装:
AB命令参数
参数说明:
测试 模拟并发请求10次,总共请求10万次。 名词解释:
服务器信息为了进行压力测试,在本地运行了一个虚拟机,可自行提取(其中包括 VMware 和 centos7,提取码:iew6,安装可参考:安装教程)。 配置信息如下:内存为 2G,处理器数量为 2 个 堆空间监控在默认不配置 JVM 堆内存大小的情况下,JVM 根据默认值来配置当前内存大小。 我们可以通过以下命令来查看堆内存配置的默认值:
可以看到,这台机器上启动的 JVM 默认最大堆内存大约为 450MB,初始化大小为 30MB左右。 GC监控调优我们还需要监控GC,JVM 中我们使用 jstat 命令监控一下 JVM 的 GC 情况。
但是这太多信息了,我们只需要查看我们需要的列:
解释:每间隔 5s 总共查询 20 次 GC 情况,且只显示13~17列的数据。
具体命令解释可参考:JDK自带性能诊断工具 JVM 内存优化案例一个高并发系统中的抢购接口,假设高峰时有上万级别的并发请求,且每次请求会产生 20KB 对象(包括订单、用户、优惠券等对象数据),像我之前做过一个机票盲盒的下单接口和机票秒杀的接口,其中就包括一些订单、用户、供应商等对象数据。 因此,虽然这里没有真实的代码场景,但是我们可以通过一个创建一个 1MB 对象的接口来模拟万级并发请求产生大量对象的场景,具体代码如下:
同时,我把 jar 包也提供出来,就是一个简单的SpringBoot项目:demo-0.0.1.jar(提取码:m5qk) 测试项目启动根据上面提供的 jar 包,我们放到 linux 指定的目录下面,然后把他启动起来:
然后我们再去查看这个JVM实例占用的堆内存大小:
由上图可知,默认情况下,堆的最大内存为456MB,Eden区为127.5MB,From和To区为4.5MB,Old区为18.5MB。 测试前准备我们主要从三个方面去统计:
10 个并发/10万总请求模拟并发请求10次,总共请求10万次。 命令:
因此,可以得出以下结论:
100 个并发/10万总请求模拟并发请求100次,总共请求10万次。 命令:
因此,可以得出以下结论:
1000 个并发/10万总请求模拟并发请求1000次,总共请求10万次。 命令:
因此,可以得出以下结论:
结果分析默认情况下,堆的最大内存为450MB,Eden区为127.5MB,From和To区为4.5MB,Old区为18.5MB。
通过上面的数据我们可以看到,GC 频率、堆内存大小会影响到的每次请求的响应时间,因为堆内存基本被用完了,所以存在大量 MinorGC 和 FullGC,这意味着我们的堆内存严重不足,因此我们可以考虑需要调大堆的内存空间来进行优化。 调优方案调优方案一:增大堆空间我们把堆空间加大到 1.5G,然后重启
然后在进行上面案例测试,因为操作一致,所以这里直接得出结论。
调优方案二:增大堆的新生代我们把堆空间加大到 1.5G,增大堆的新生代的内存空间,并且Eden区和Survivor区按照
然后在进行上面案例测试,因为操作一致,所以这里直接得出结论。
调优策略我们把上面3种情况总结如下: 一般情况下,高并发业务场景中,需要一个比较大的堆空间,而默认参数情况下,堆空间不会很大,所以我们需要进行调整。 但是不要单纯的调整堆的总大小,要调整新生代和老年代的比例,以及 Eden 区还有 From 区,还有 To 区的比例。 所以在我们上述的测试中,调整方案二,得到结果是最好的。在三种测试情况下都能够有非常好的性能指标,同时 GC 耗时相对控制也较好。 对于方案一,就是单纯的加大堆空间,里面的比例不适合高并发场景,反而导致堆空间变大,没有明显减少 GC 的次数,但是每次 GC 需要检索对象的堆空间更大,所以 GC 耗时更长。 对于方案二,调整为一个很大的新生代和一个较小的老年代,原因是这样可以尽可能回收掉大部分短期对象(大部分对象朝生夕死),减少中期的对象,而老年代尽存放长期存活对象。 通过上面的场景测试,我们可以知道影响其性能主要原因是由于新生代空间较小,Eden 区很快被填满,就会导致频繁 Minor GC,因此我们可以通过增大新生代空间来降低 Minor GC 的频率。 性能分析一次 Minor GC 时间是由两部分组成:T1(扫描新生代)和 T2(复制存活对象)。 因此: 默认情况:假设一个对象在 Eden 区的存活时间为 500ms,Minor GC 的时间间隔是 300ms,因为这个对象 方案一:整堆空间加大,但是新生代没有增大多少(只增加了一点点),那么正常情况下,新生代的扫描时间肯定会变长(假设为原来1.5倍的T1),假设对象在 Eden 区的存活时间为 500ms,Minor GC 的时间可能会扩大到 400ms,因为这个对象 方案二:当我们增大新生代空间,假设新生代的扫描时间为原来的2倍(2 * T1),因此Minor GC 的时间间隔可能会扩大到 600ms,假设对象在 Eden 区的存活时间为 500ms,此时这个对象 可见,扩容后,Minor GC 时增加了 T1,但省去了 T2 的时间。 在 JVM 中,复制对象的成本要远高于扫描成本。如果在堆内存中存在较多的长期存活的对象,此时增加年轻代空间,反而会增加 Minor GC 的时间。如果堆中的短期对象很多,那么扩容新生代,单次 Minor GC 时间不会显著增加。因此,单次 Minor GC 时间更多取决于 GC 后存活对象的数量,而非 Eden 区的大小。 这个就解释了在上面2中内存调整方案中,都是增大内存,方案一为什么性能还差些,但是到了方案二话,性能就有明显的上升。所以在面试中经如果被问到怎么调优,不仅仅是一句简单的加内存就完事了。 推荐策略主要从堆的新生代和老年的内存大小以及功能性需求推荐。 新生代大小
老年代大小
GC优化在上面的案例中,如果说要追求响应时间,那就得通过GC优化。 GC 性能衡量指标吞吐量 这里的衡量吞吐量是指应用程序所花费的时间和系统总运行时间的比值。我们可以按照这个公式来计算:
如果系统运行了 100 分钟,GC 耗时 1 分钟,则系统吞吐量为 99%。一般来说,GC 的吞吐量不能低于 95%。 停顿时间 指垃圾回收器正在运行时,应用程序的暂停时间(即STW)。对于串行回收器而言,停顿时间可能会比较长,而使用并发回收器,由于垃圾收集器和应用程序交替运行,程序的停顿时间就会变短,但其效率很可能不如独占垃圾收集器,系统的吞吐量也很可能会降低。 垃圾回收频率 通常垃圾回收的频率越低越好,增大堆内存空间可以有效降低垃圾回收发生的频率,但同时也意味着堆积的回收对象越多,最终也会增加回收时的停顿时间。所以我们需要适当地增大堆内存空间,保证正常的垃圾回收频率即可。 分析 GC 日志通过 JVM 参数预先设置 GC 日志,几种 JVM 参数设置如下:
正常的话GC日志一般来说都是都是运维人员操作,我们这里通过虚拟机来测试。 1000 个并发/10万总请求:ab -c 1000 -n 100000 http://127.0.0.1:8080/blind/order 默认情况
调优方案二
怎么分析得到两次GC日志以后,使用日志工具 gcViewer(提取码:r9bh)来分析,官方使用文档:点击。
很明显第一个暂停总耗时比第二个要多很多,一个是 40 秒,一个是 18 秒左右,相差很多,这个本质上也可以分析出来,对于系统来说,第二个的 GC 日志情况更加的好。 调优策略降低 Minor GC 频率增大新生代空间,即调优方案二。新生代空间增大,也就意味着未死的对象会增多,如果未进入老年代,则下次GC回收对象会变多,若进入老年代,同时也会影响下次Full GC。 降低 Full GC 的频率由于堆内存空间不足或老年代对象太多,会触发 Full GC,频繁的 Full GC 会带来上下文切换,增加系统的性能开销。 减少创建大对象在平常的业务场景中,我们一次性从数据库中查询出一个大对象用于 web 端显示。比如,一次性查询出 60 个字段的业务操作,这种大对象如果超过年轻代最大对象阈值,会被直接创建在老年代,即使被创建在了年轻代,由于年轻代的内存空间有限,通过 Minor GC 之后也会进入到老年代。这种大对象很容易产生较多的 Full GC。 增大堆内存空间在堆内存不足的情况下,增大堆内存空间,且设置初始化堆内存为最大堆内存,也可以降低 Full GC 的频率。 选择合适的 GC 回收器如果要求每次操作的响应时间必须在 500ms 以内。这个时候我们一般会选择响应速度较快的 GC 回收器,堆内存比较小的情况下(小于6G)选择 CMS(Concurrent Mark Sweep)回收器,堆内存比较大的情况下(大于8G选择)G1 回收器。 总结GC 调优是个很复杂、很细致的过程,要根据实际情况调整,不同的机器、不同的应用、不同的性能要求调优的手段都是不同的,这些都需要平时去积累,去观察,去实践。一般调优的思路:
需要注意的是任何调优都需要结合场景,明确已知问题和性能目标,绝大部分情况下,并不需要特意去调优JVM,如果出现问题,我们最主要做的应该是去检查我们的代码,以免引入新的 Bug,不能为了调优而调优,以免带来风险和弊端。 |
|
开发测试 最新文章 |
pytest系列——allure之生成测试报告(Wind |
某大厂软件测试岗一面笔试题+二面问答题面试 |
iperf 学习笔记 |
关于Python中使用selenium八大定位方法 |
【软件测试】为什么提升不了?8年测试总结再 |
软件测试复习 |
PHP笔记-Smarty模板引擎的使用 |
C++Test使用入门 |
【Java】单元测试 |
Net core 3.x 获取客户端地址 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/18 0:28:55- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |