热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JVM性能调优之内存优化与GC优化

JVM 调优是一个系统而又复杂的过程,但我们知道,在大多数情况下,我们基本不用去调整 JVM 内存分配,因为一些初始化的参数已经可以保证应用 服务正常稳定地工作了。 在应用服务的特定场景下,JVM 内存分配不合理带来的性能表现并不会像内存溢出问题这么突出。一般你没有深入到各项性能指标中去,是很难发现其 中隐藏的性能损耗

一、压测工具AB

Ab(ApacheBench) 测试工具是 Apache 提供的一款测试工具,具有简单易上手的特点,在测试 Web 服务时非常实用。 ab 一般都是在 Linux 上用。 安装非常简单,只需要在 Linux 系统中输入 yum -y install httpd-tools 命令,就可以了。 安装成功后,输入 ab 命令,可以看到以下信息:
JVM 性能调优之内存优化与 GC 优化
ab 工具用来测试 post get 接口请求非常便捷,可以通过参数指定请求数、并发数、请求参数等
测试 get 请求接口:
ab -c 10 -n 100 http://www.test.api.com/test/login?userName=test&password=test
测试 post 请求接口:
ab -n 100 -c 10 -p ‘post.txt’ -T ‘application/x-www-form-urlencoded’ ‘http://test.api.com/test/register’
post.txt 为存放 post 参数的文档,存储格式如 usernanme=test&password=test&sex=1
参数的含义:
-n:总请求次数(最小默认为 1);
-c:并发次数(最小默认为 1 且不能大于总请求次数,例如:10 个请求,10 个并发,实际就是 1 人请求 1 次);
-p:post 参数文档路径(-p 和 -T 参数要配合使用);
-T:header 头内容类型(此处切记是大写英文字母 T);
JVM 性能调优之内存优化与 GC 优化
Requests per second:吞吐率,指某个并发用户数下单位时间内处理的请求数; Time per request:上面的是用户平均请求等待时间,指处理完成所有请求数所花费的时间 /(总请求数 / 并发用户数); Time per request:下面的是服务器平均请求处理时间,指处理完成所有请求数所花费的时间 / 总请求数; Percentage of the requests served within a certain time:每秒请求时间分布情况,指在整个请求中,每个请求的时间长度的分布情况,例如有 50% 的请求响应在 8ms 内,66% 的请求响应在 10ms 内,说明有 16% 的请求在 8ms~10ms 之间。

二、JVM内存分配的调优案例

一个高并发系统中的抢购接口,高峰时 5W 的并发请求,且每次请求会产生 20KB 对象(包括订单、用户、优惠券等对象数据)。 我们可以通过一个并发创建一个 1MB 对象的接口来模拟万级并发请求产生大量对象的场景,具体代码如下:
JVM 性能调优之内存优化与 GC 优化

AB压测

对应用服务进行压力测试,模拟不同并发用户数下的服务的响应情况:
1、10 个并发用户/10 万请求量(总)
2、100 个并发用户/10 万请求量(总)
3、1000 个并发用户/10 万请求量(总)

ab -c 10 -n 100000 http://127.0.0.1:8080/jvm/heap
ab -c 100 -n 100000 http://127.0.0.1:8080/jvm/heap
ab -c 1000 -n 100000 http://127.0.0.1:8080/jvm/heap

服务器信息

我本机起一台 Linux 虚拟机,分配的内存为 2G,处理器数量为 2 个。具体信息如下图:
JVM 性能调优之内存优化与 GC 优化
JVM 性能调优之内存优化与 GC 优化

GC监控

还有一句话,无监控不调优,所以我们需要监控起来。JVM 中我们使用 jstat 命令监控一下 JVM 的 GC 情况。 统计 GC 的情况。
jstat-gc 8404 5000 20 | awk '{print $13,$14,$15,$16,$17}
JVM 性能调优之内存优化与 GC 优化

堆空间监控

在默认不配置 JVM 堆内存大小的情况下,JVM 根据默认值来配置当前内存大小。 我们可以通过以下命令来查看堆内存配置的默认值:
java -XX:+PrintFlagsFinal -version | grep HeapSize
JVM 性能调优之内存优化与 GC 优化
这台机器上启动的 JVM 默认最大堆内存为 480MB,初始化大小为 32MB。

测试项目启动

JVM 性能调优之内存优化与 GC 优化
使用 jmap -heap 这种方式,我们看到这个 JVM 应用占据的堆空间大小

压测结果

1、10 个并发用户/10 万请求量(总)
使用 AB 进行压力测试: ab -c 10 -n 100000 http://127.0.0.1:8080/jvm/heap
JVM 性能调优之内存优化与 GC 优化
JVM 性能调优之内存优化与 GC 优化
统计 GC 情况
jstat -gc 9656 5000 20 | awk '{print $13,$14,$15,$16,$17
JVM 性能调优之内存优化与 GC 优化
测试结果显示:
用户的吞吐量大于在 1426/每秒左右
JVM 服务器平均请求处理时间 0.7ms 左右
JVM 服务器发生了 2700 多次 YGC,耗时 15 秒 ,还有 45 次 FGC,2.3 秒左右,加在一起 GC 耗时 17 秒

2、100 个并发用户/10 万请求量(总)
使用 AB 进行压力测试:
ab -c 100 -n 100000 http://127.0.0.1:8080/jvm/heap
JVM 性能调优之内存优化与 GC 优化
测试结果显示:
用户的吞吐量大于在 1262/每秒左右
JVM 服务器平均请求处理时间 0.8ms 左右
JVM 服务器发生了 2700 多次 YGC,耗时 30 秒 ,还有 56 次 FGC,3 秒左右,加在一起 GC 耗时 33 秒
3、1000 个并发用户/10 万请求量(总)
使用 AB 进行压力测试: ab -c 1000 -n 100000 http://127.0.0.1:8080/jvm/heap
JVM 性能调优之内存优化与 GC 优化
测试结果显示:
用户的吞吐量大于在 1145/每秒左右
JVM 服务器平均请求处理时间 0.8ms 左右
JVM 服务器发生了 2700 多次 YGC,耗时 38 秒 ,还有 47 次 FGC,3 秒左右,加在一起 GC 耗时 42 秒

结果分析
GC频率

高频的 FullGC 会给系统带来非常大的性能消耗,虽然 MinorGC 相对 FullGC 来说好了许多,但过多的 MinorGC 仍会给系统带来压力

内存

这里的内存指的是堆内存大小,堆内存又分为年轻代内存和老年代内存。堆内存不足,会增加 MinorGC ,影响系统性能

吞吐量

频繁的 GC 将会引起线程的上下文切换,增加系统的性能开销,从而影响每次处理的线程请求,最终导致系统的吞吐量下降

延时

JVM 的 GC 持续时间也会影响到每次请求的响应时间

调优方案
调优方案一

调整堆内存空间减少 GC:通过分析,堆内存基本被用完了,而且存在大量 MinorGC 和 FullGC,这意味着我们的堆内存严重不足,这个时候我们需要调 大堆内存空间
堆空间加大到 1.5G
java -jar -Xms1500m -Xmx1500m jvm-1.0-SNAPSHOT.jar
使用 AB 进行压力测试:
ab -c 10 -n 100000 http://127.0.0.1:8080/jvm/heap
JVM 性能调优之内存优化与 GC 优化
测试结果显示:
用户的吞吐量大于在 1205/每秒左右
JVM 服务器平均请求处理时间 0.83ms 左右
JVM 服务器发生了 800 次 YGC,耗时 33 秒 ,还有 1 次 FGC,1 秒左右,加在一起 GC 耗时 34 秒

使用 AB 进行压力测试: ab -c 100 -n 100000 http://127.0.0.1:8080/jvm/heap
JVM 性能调优之内存优化与 GC 优化
测试结果显示:
用户的吞吐量大于在 989/每秒左右
JVM 服务器平均请求处理时间 1.01ms 左右
JVM 服务器发生了 800 次 YGC,耗时 46 秒 ,还有 8 次 FGC,6 秒左右,加在一起 GC 耗时 52 秒

使用 AB 进行压力测试: ab -c 1000 -n 100000 http://127.0.0.1:8080/jvm/heap
JVM 性能调优之内存优化与 GC 优化
测试结果显示:
用户的吞吐量大于在 749/每秒左右
JVM 服务器平均请求处理时间 1.3ms 左右
JVM 服务器发生了 800 次 YGC,耗时 66 秒 ,还有 8 次 FGC,9 秒左右,加在一起 GC 耗时 75 秒

调整方案二

java -jar -Xms1500m -Xmx1500m -Xmn1000m -XX:SurvivorRatio=8 jvm-1.0-SNAPSHOT.jar
使用 AB 进行压力测试: ab -c 10 -n 100000 http://127.0.0.1:8080/jvm/heap

JVM 性能调优之内存优化与 GC 优化
测试结果显示:
用户的吞吐量大于在 1780/每秒左右
JVM 服务器平均请求处理时间 0.56ms 左右
JVM 服务器发生了 400 次 YGC,耗时 5.8 秒 ,还有 2 次 FGC,0.1 秒左右,加在一起 GC 耗时 6 秒

使用 AB 进行压力测试: ab -c 100 -n 100000 http://127.0.0.1:8080/jvm/heap
JVM 性能调优之内存优化与 GC 优化
测试结果显示:
用户的吞吐量大于在 1927/每秒左右
JVM 服务器平均请求处理时间 0.51ms 左右
JVM 服务器发生了 400 多次 YGC,耗时 11 秒 ,没有 FGC,加在一起 GC 耗时 11 秒

使用 AB 进行压力测试: ab -c 1000 -n 100000 http://127.0.0.1:8080/jvm/heap
JVM 性能调优之内存优化与 GC 优化
测试结果显示:
用户的吞吐量大于在 1657/每秒左右
JVM 服务器平均请求处理时间 0.6ms 左右
JVM 服务器发生了 400 多次 YGC,耗时 14 秒 ,还 1 次 FGC,3 秒左右,加在一起 GC 耗时 17 秒

内存优化总结

JVM 性能调优之内存优化与 GC 优化
一般情况下,高并发业务场景中,需要一个比较大的堆空间,而默认参数情况下,堆空间不会很大。所以我们有必要进行调整。 但是不要单纯的调整堆的总大小,要调整新生代和老年代的比例,以及 Eden 区还有 From 区,还有 To 区的比例。 所以在我们上述的测试中,调整方案二,得到结果是最好的。在三种测试情况下都能够有非常好的性能指标,同时 GC 耗时相对控制也较好。 对于调整方案一,就是单纯的加大堆空间,里面的比例不适合高并发场景,反而导致堆空间变大,没有明显减少 GC 的次数,但是每次 GC 需要检索对象 的堆空间更大,所以 GC 耗时更长。 方案二:调整为一个很大的新生代和一个较小的老年代.原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而老年代尽存放长期存活对象。 由于新生代空间较小,Eden 区很快被填满,就会导致频繁 Minor GC,因此我们可以通过增大新生代空间来降低 Minor GC 的频率。 单次 Minor GC 时间是由两部分组成:T1(扫描新生代)和 T2(复制存活对象)

默认情况:一个对象在 Eden 区的存活时间为 500ms,Minor GC 的时间间隔是 300ms,因为这个对象存活时间>间隔时间,那么正常情况下,Minor GC 的时间为 :T1+T2。

方案一:整堆空间加大,但是新生代没有增大多少,对象在 Eden 区的存活时间为 500ms,Minor GC 的时间可能会扩大到 400ms,因为这个对象存 活时间>间隔时间,那么正常情况下,Minor GC 的时间为 :T1*1.5(Eden 区加大了)+T2

方案二:当我们增大新生代空间,Minor GC 的时间间隔可能会扩大到 600ms,此时一个存活 500ms 的对象就会在 Eden 区中被回收掉,此时就不 存在复制存活对象了,所以再发生 Minor GC 的时间为:即 T12(空间大了)+T20 可见,扩容后,Minor GC 时增加了 T1,但省去了 T2 的时间。 在 JVM 中,复制对象的成本要远高于扫描成本。如果在堆内存中存在较多的长期存活的对象,此时增加年轻代空间,反而会增加 Minor GC 的时间。如 果堆中的短期对象很多,那么扩容新生代,单次 Minor GC 时间不会显著增加。因此,单次 Minor GC 时间更多取决于 GC 后存活对象的数量,而非 Eden 区的大小。 这个就解释了之前的内存调整方案中,方案一为什么性能还差些,但是到了方案二话,性能就有明显的上升。

推荐策略
  1. 新生代大小选择
    • 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择).在此种情况下,新生代收集发生的频率也是最小 的.同时,减少到达老年代的对象.
    • 吞吐量优先的应用:尽可能的设置大,可能到达 Gbit 的程度.因为对响应时间没有要求,垃圾收集可以并行进行,一般适合 8CPU 以上的应用.
    • 避免设置过小.当新生代设置过小时会导致:1.MinorGC 次数更加频繁 2.可能导致 MinorGC 对象直接进入老年代,如果此时老年代满了,会触 发 FullGC.
      2.老年代大小选择
    • 响应时间优先的应用:老年代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数.如果堆设置小了,可 以会造成内存碎 片,高回收频率以及应用暂停而使用传统的标记清除方式
    • 如果堆大了,则需要较长的收集时间.最优化的方案,一般需要参考以下数据获得:
    • 并发垃圾收集信息、持久代并发收集次数、传统 GC 信息、花在新生代和老年代回收上的时间比例。 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的新生代和一个较小的老年代.原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而 老年代尽存放长期存活对象

推荐阅读
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文详细介绍了解决全栈跨域问题的方法及步骤,包括添加权限、设置Access-Control-Allow-Origin、白名单等。通过这些操作,可以实现在不同服务器上的数据访问,并解决后台报错问题。同时,还提供了解决second页面访问数据的方法。 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • 本文讨论了如何在codeigniter中识别来自angularjs的请求,并提供了两种方法的代码示例。作者尝试了$this->input->is_ajax_request()和自定义函数is_ajax(),但都没有成功。最后,作者展示了一个ajax请求的示例代码。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • 31.项目部署
    目录1一些概念1.1项目部署1.2WSGI1.3uWSGI1.4Nginx2安装环境与迁移项目2.1项目内容2.2项目配置2.2.1DEBUG2.2.2STAT ... [详细]
  • 本文介绍了一个React Native新手在尝试将数据发布到服务器时遇到的问题,以及他的React Native代码和服务器端代码。他使用fetch方法将数据发送到服务器,但无法在服务器端读取/获取发布的数据。 ... [详细]
author-avatar
Ace狂_338
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有