本文介绍了一次生产环境的JVM GC相关参数的调优过程,通过参数的调整避免了GC卡顿对JAVA服务成功率的影响。

这段时间在整理jvm系列的文章,无意中发现本文,作者思路清晰通过步步分析最终解决问题。我个人特别喜欢这种实战类的内容,经原作者的授权同意,将文章分享于此。原文链接:Java服务GC参数调优案例,下面为转载此文的内容,备注部分为本人添加,主要起到说明的作用。

背景以及遇到的问题

我们的Java HTTP服务属于OLTP类型,对成功率和响应时间的要求比较高,在生产环境中出现偶现的成功率突然下降然后又自动恢复的情况,如图所示:

JVM和GC相关的参数如下:

-Xmx22528m
-Xms22528m
-XX:NewRatio=2
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled

总结来说,由于服务中大量使用了Cache,所以堆大小开到了22G。GC算法使用CMS(UseConcMarkSweepGC),开启了降低标记停顿(CMSParallelRemarkEnabled),设置年轻代为并行收集(UseParNewGC),年轻代和老年代的比例为1:2 (NewRatio=2).

JVM GC日志相关的参数如下:

-Xloggc:/data/gc.log
-XX:GCLogFileSize=10M
-XX:NumberOfGCLogFiles=10
-XX:+UseGCLogFileRotation
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:+PrintGCDetails
-XX:+DisableExplicitGC
-verbose:gc

问题解决过程

排除应用程序的内存使用问题

首先使用jmap查看内存使用情况:

jmap -histo:live PID

这个命令把程序中当前的对象按照个数和占用的空间排序以后打印出来。这里没有发现使用异常的对象。

排除Cache内容过多的问题

如果Cache内容过多也会导致JVM老年代容易被用满导致频繁GC,因此调出GC日志进行查看,发现每次GC以后内存使用一般是从20G降低到5G左右,因此常驻内存的Cache不是导致GC长时间卡顿的根本原因。对于GC LOG的查看有多种方式,使用VisualVM比较直观,需要使用VisualGC:

从图中我们可以看到伊甸园和老年代的空间分配,由于整体内存是20G,设置 -XX:NewRatio=2 因此老年代是14G,伊甸园+S0+S1=7G

调整GC时间点(成功率抖动问题加重)

如果GC需要处理的内存量比较大,执行的时间也就比较长,STW (Stop the World)时间也就更长。按照这个思路调整CMS启动的时间点,希望提早GC,也就是让GC变得更加频繁但是期望每次执行的时间较少。添加了下面这两个参数:

-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=50

意思是说在Old区使用了50%的时候触发GC。实验后发现GC的频率有所增加,但是每次GC造成的陈功率降低现象并没有减弱,因此弃用这两个参数。

调整对象在年轻代内存中驻留的时间(效果不明显)

如果能够降低老年代GC的频率也可以达到降低GC影响的目的,因此尝试让对象在年轻代内存中进行更长时间的驻留,提升这些对象在年轻代GC时候被销毁的概率。使用参数-XX:MaxTenuringThreshold=31调整以后收效不明显。

备注:
1、MaxTenuringThreshold 在1.5.0_05之前最大值可以设置为31 ,1.5.0_06以后最大值可以设置为15,超过15会被认为无限大。参考:Never set GC parameter -XX:MaxTenuringThreshold greater than 15
2、提升年轻代GC被销毁的概率,只是调整这个参数效果不大,第二次age的值会重新计算,参考:说说MaxTenuringThreshold这个参数

CMS-Remark之前强制进行年轻代的GC

首先补充一下CMS的相关知识,在CMS整个过程中有两个步骤是STW的,如图红色部分:

CMS并非没有暂停,而是用两次短暂停来替代串行标记整理算法的长暂停,它的收集周期是这样:

  • 1、初始标记(CMS-initial-mark),从root对象开始标记存活的对象
  • 2、并发标记(CMS-concurrent-mark)
  • 3、重新标记(CMS-remark),暂停所有应用程序线程,重新标记并发标记阶段遗漏的对象(在并发标记阶段结束后对象状态的更新导致)
  • 4、并发清除(CMS-concurrent-sweep)
  • 5、并发重设状态等待下次CMS的触发(CMS-concurrent-reset)。

通过GC日志和成功率下降的时间点进行比对发现并不是每一次老年代GC都会导致成功率的下降,但是从中发现了一个规律:

前两次GC CMS-Remark过程在4s左右造成了成功率的下降,但是第三次GC并没有对成功率造成明显的影响,CMS-Remark只有0.18s。Java HTTP 服务是通过Nginx进行反向代理的,nginx设置的超时时间是3s,所以如果GC卡顿在3s以内就不会对成功率造成太大的影响。

从GC日志中又发现一个信息:

在文档和相关资料中没有找到蓝色部分的含义,猜测是remark处理的内存量,处理的越多就越慢。添加下面两个参数强制在remark阶段和FULL GC阶段之前先在进行一次年轻代的GC,这样需要进行处理的内存量就不会太多。

备注:
1、蓝色部分的含义:remark标记需要清理对象的容量。关于如何分析CMS日志,可以参考这篇文章:了解 CMS 垃圾回收日志
2、FULL GC阶段之前先在进行一次年轻代的GC的意义是:Yong区对象引用了Old区的对象,如果在Old区进行清理之前不进行Yong区清理,就会导致Old区被yong区引用的对象无法释放。可以参考这篇文章:假笨说-又抓了一个导致频繁GC的鬼–数组动态扩容

-XX:+ScavengeBeforeFullGC
-XX:+CMSScavengeBeforeRemark

调优以后效果很明显,下面是两台配置完全相同的服务器在同一时间段的成功率和响应时间监控图,第一个没有添加强制年轻代GC的参数。


结论

1、在CMS-remark阶段需要对堆中所有的内存对象进行处理,如果在这个阶段之前强制执行一次年轻代的GC会大量减少remark需要处理的内存数量,进而降低JVM卡顿对成功率的影响。
2、对于Java HTTP服务,JVM的卡顿时间应该小于HTTP客户端的调用超时时间,否则JVM卡顿会对成功率造成影响。

jvm系列(六):Java服务GC参数调优案例的更多相关文章

  1. JVM系列【6】GC与调优2.md

    JVM系列笔记目录 虚拟机的基础概念 class文件结构 class文件加载过程 jvm内存模型 JVM常用指令 GC与调优 了解HotSpot常用命令行参数 JVM的命令行参数参考: https:/ ...

  2. JVM系列【6】GC与调优5-日志分析

    JVM系列笔记目录 虚拟机的基础概念 class文件结构 class文件加载过程 jvm内存模型 JVM常用指令 GC与调优 主要内容 分析PS.CMS.G1的回收日志,目标使大概能读懂GC日志. 测 ...

  3. JVM系列【6】GC与调优1

    JVM系列笔记目录 虚拟机的基础概念 class文件结构 class文件加载过程 jvm内存模型 JVM常用指令 GC与调优 GC基础知识 什么是垃圾 ​ 没有任何引用指向的一个对象或多个对象(循环引 ...

  4. JVM 内存分配、调优案例

    内存分配 对象优先在Eden区分配 大多数情况下,对象在新生代Eden区中分配.当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC. HotSpot虚拟机提供了-XX:+PrintG ...

  5. JVM性能参数调优实践,不会执行Full GC,网站无停滞

    原文来自:http://bbs.csdn.net/topics/310110257 本文只做整理记录,供个人学习. 1 JVM参数调优是个很头痛的问题,设置的不好,JVM不断执行Full GC,导致整 ...

  6. 性能测试三十六:内存溢出和JVM常见参数及JVM参数调优

    堆内存溢出: 此种溢出,加内存只能缓解问题,不能根除问题,需优化代码堆内存中存在大量对象,这些对象都有被引用,当所有对象占用空间达到堆内存的最大值,就会出现内存溢出OutOfMemory:Java h ...

  7. JVM参数调优

    JVM参数调优 JVM参数调优是一个很头痛的问题,可能和应用有关系,下面是本人一些调优的实践经验,希望对读者能有帮助,环境LinuxAS4,resin2.1.17,JDK6.0,2CPU,4G内存,d ...

  8. JVM内存结构、参数调优和内存泄露分析

    1. JVM内存区域和参数配置 1.1 JVM内存结构 Java堆(Heap) Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建.此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都 ...

  9. (转)JVM参数调优八大技巧

    这里和大家分享一下JVM参数调优的八条经验,JVM参数调优,这是很头痛的问题,设置的不好,JVM不断执行FullGC,导致整个系统变得很慢,网站停滞时间能达10秒以上,相信通过本文的学习你对JVM参数 ...

随机推荐

  1. 使用RabbitMQ做数据接收和处理时,自动关闭

    场景:N个客户端向MQ里发送数据:服务器上有另一个控制台程序(假设叫ServerClient)来处理这里数据(往数据库保存).方向为Client * n→MQSERVER→ServerClient 问 ...

  2. 从微信小程序开发者工具源码看实现原理(四)- - 自适应布局

    从前面从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计可以知道,小程序大部分是通过web技术进行渲染的,也就是最终通过浏览器的dom tree + cssom来生成渲染树:既然最终是通 ...

  3. iOS中基于协议的路由设计

    一.背景 前段时间对我们自己的App做了结构上的重构,抛弃了之前简单的MVC开发模式,原因是随着App的业务线越来越多,单个页面的功能越来越复杂,MVC开发模式导致整个Controller-layer ...

  4. 2019杭电多校第二场hdu6602 Longest Subarray(线段树)

    Longest Subarray 题目传送门 解题思路 本题求一个最大的子区间,满足区间内的数字要么出现次数大于等于k次,要么没出现过.给定区间内的数字范围是1~c. 如果r为右边界,对于一种数字x, ...

  5. git基础学习

    1.git是什么 内容寻址文件系统,分布式版本控制系统 2.git作用 开发过程中的版本控制 3.git基础命令 克隆git仓库---clone:git clone 仓库url 选分支---check ...

  6. angularjs通信以及postmessage与iframe通信

    这篇文章是用markdown工具写的,有需要的可以使用vscode打开   # angularjs 控制器.组件之间的通信 ## 一.基于事件的方式 此方式下,主要通过 angularjs 内置指令` ...

  7. UE4 代理 BindRaw和BindUObject

    代理允许您在C++对象上以通用的但类型安全的方式调用成员函数.通过使用代理,可以将其动态地绑定到任何对象的成员函数上,然后在该对象上调用函数,即时调用者不知道该对象的类型也没关系. 任何时候都应该通过 ...

  8. OOM和JVM配置优化

    OOM这个缩写就是Java程序开发过程中让人最头痛的问题:Out of Memory.在很多开发人员的开发过程中,或多或少的都会遇到这类问题,这类问题定位比较困难,往往需要根据经验来判断可能出现问题的 ...

  9. Java匹马行天下之J2EE框架开发——Spring—>用IDEA开发Spring程序(01)

    一.心动不如行动 一.创建项目 *注:在IDEA中我创建的Maven项目,不了解Maven的朋友可以看我之前的博客“我们一起走进Maven——知己知彼”,了解Maven后可以看我之前的博客“Maven ...

  10. 【Java例题】7.1 线程题1-时间显示线程

    1.时间显示线程.设计一个示线程子类,每秒钟显示一次当前时间:然后编写主类,在主函数中定义一个线程对象,并启动这个线程 package chapter7; import java.text.Simpl ...