记一次线上gc调优的过程

近期公司运营同学经常表示线上我们一个后台管理系统运行特别慢,而且经常出现504超时的情况。对于这种情况我们本能的认为可能是代码有性能问题,可能有死循环或者是数据库调用次数过多导致接口运行过慢。应领导要求,我们将主站中进行性能测试的框架代码(见我前面一篇博文记录一次通过性能日志处理线上性能问题的过程)添加到了该后台管理系统中。上线运行一段时间后,查看相关日志可以看到如下分析日志:

通过该日志可以发现,dao方法一直获取不到数据库链接池,但是根据实际情况考虑应该不大可能,原因有两点:

  • 主站和后台管理系统使用的是同一套数据库,相较而言,主站的访问量更高,数据量也更大,却没有出现问题;
  • 该性能问题的出现有一定的频率,即有的时候出现,有的时候情况好一些;

虽然根据分析我们认为不大可能是数据库问题,但我们还是查看了线上数据库链接的相关情况,具体链接情况如下:

这个两个是主要用到的两个数据库,而其他的数据库链接相对来说链接数也不高,总链接数加起来远远没有达到我们配置的最大链接数,并且在processlist中也没有发现什么耗时比较高的链接。因而确实证实了出现的性能问题不是数据库导致的。

既然不是数据库导致的,通过性能日志也可以确认不是代码中有死循环或者数据库调用次数过多的问题,我们就思考是否为jvm层面的问题。在登录线上机器之后,我们使用首先使用top命令查看了该机器进程的运行情况:

可以看到,id为2580的进程cpu一直在100%以上,然后使用如下命令查看该进程中具体是哪个线程运行的cpu如此之高:

top -Hp 2580

结果如下:

可以看到,id为2598的线程运行cpu达到了97.7%,基本上可以确定是这个线程的运行导致项目整体运行较慢。接下来我们使用jstack命令查看了该进行各个线程运行情况,具体的命令如下:

jstack 2580 > ~/jstack.log

这里有两点需要注意:

  • jstack命令需要在jdk的bin目录下执行,并且必须要以当前启动项目tomcat的用户身份运行,如果不是该用户登录的,可以使用如下命令导出线程运行日志:
sudo -u admin ~/jdk/bin/jstack 2580 > ~/jstack.log
  • 在线程日志中,线程的id是使用十六进制的方式打印的,而在top -Hp命令中线程的id是10进制打印的,因而我们需要得到2598这个数字对应的16进制值,命令如下:
[[email protected] bin]$ printf "%x\n" 2598
a26

在导出的jstack.log中我们找到了该线程的运行情况,结果如下:

"main" #1 prio=5 os_prio=0 tid=0x00007f25bc00a000 nid=0xa15 runnable [0x00007f25c3fe6000]
   java.lang.Thread.State: RUNNABLE
    at java.net.PlainSocketImpl.socketAccept(Native Method)
    at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
    at java.net.ServerSocket.implAccept(ServerSocket.java:545)
    at java.net.ServerSocket.accept(ServerSocket.java:513)
    at org.apache.catalina.core.StandardServer.await(StandardServer.java:446)
    at org.apache.catalina.startup.Catalina.await(Catalina.java:713)
    at org.apache.catalina.startup.Catalina.start(Catalina.java:659)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:351)
    at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:485)

"VM Thread" os_prio=0 tid=0x00007f25bc120800 nid=0xa26 runnable

"Gang worker#0 (Parallel GC Threads)" os_prio=0 tid=0x00007f25bc01b000 nid=0xa16 runnable

"Gang worker#1 (Parallel GC Threads)" os_prio=0 tid=0x00007f25bc01d000 nid=0xa17 runnable

"Gang worker#2 (Parallel GC Threads)" os_prio=0 tid=0x00007f25bc01e800 nid=0xa18 runnable

"Gang worker#3 (Parallel GC Threads)" os_prio=0 tid=0x00007f25bc020800 nid=0xa19 runnable

"Gang worker#4 (Parallel GC Threads)" os_prio=0 tid=0x00007f25bc022800 nid=0xa1a runnable

"Gang worker#5 (Parallel GC Threads)" os_prio=0 tid=0x00007f25bc024000 nid=0xa1b runnable

可以看到,下方的"VM Thread"就是该cpu消耗较高的线程,查看相关文档我们得知,VM Thread是JVM层面的一个线程,主要工作是对其他线程的创建,分配和对象的清理等工作的。从后面几个线程也可以看出,JVM正在进行大量的GC工作。这里的原因已经比较明显了,即大量的GC工作导致项目运行缓慢。那么具体是什么原因导致这么多的GC工作呢,我们使用了jstat命令查看了内存使用情况:

[[email protected] bin]$ jstat -gcutil 2580 1000 5
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00  98.07 100.00 100.00  96.04  92.74   2587   98.216 17803 25642.557 25740.773
  0.00 100.00 100.00 100.00  96.04  92.74   2587   98.216 17804 25644.777 25742.993
  0.00 100.00 100.00 100.00  96.04  92.74   2587   98.216 17804 25644.777 25742.993
  0.00  91.59 100.00 100.00  96.04  92.74   2587   98.216 17805 25646.981 25745.197
  0.00 100.00 100.00 100.00  96.04  92.74   2587   98.216 17806 25647.339 25745.555

可以看到Suvivor space1、Eden Space和Old Space等内存使用情况几乎都达到了100%,并且Young GC和Full GC运行时间和次数也非常高。接着我们使用了jmap查看了内存中创建的对象情况,结果如下:

[[email protected] bin]$ jmap -histo 2580

 num     #instances         #bytes  class name
----------------------------------------------
   1:       3658294      324799888  [C
   2:       6383293      153199032  java.util.LinkedList$Node
   3:       6369472      152867328  java.lang.Long
   4:       6368531      152844744  com.homethy.lead.sites.util.SiteContextUtil$Node
   5:       3631391       87153384  java.lang.String
   6:         28357       30078512  [B
   7:        135622       13019712  java.util.jar.JarFile$JarFileEntry
   8:        132602       11668976  java.lang.reflect.Method
   9:        224247        7175904  java.util.HashMap$Node
  10:         46394        5601504  [Ljava.util.HashMap$Node;
  11:        145769        4664608  java.util.concurrent.ConcurrentHashMap$Node
  12:         81843        3273720  java.util.LinkedHashMap$Entry
  13:         57903        3209512  [Ljava.lang.Object;
  14:         56976        3190656  java.util.LinkedHashMap
  15:         20857        2446960  java.lang.Class
  16:         45890        2202720  org.aspectj.weaver.reflect.ShadowMatchImpl

可以看到,SiteContextUtil类中的Node对象创建数量非常高,而LinkedList.Node和java.lang.Long的对象数量和SiteContextUtil.Node的数量几乎一致,结合具体的业务情况我们知道SiteContextUtil.Node对象是放在LinkedList.Node中的,因而可以确认就是SiteContextUtil.Node的数量较高,创建频率较快导致jvm进行了大量的gc工作,最终导致工程性能降低。

原文地址:https://www.cnblogs.com/zhangxufeng/p/9162181.html

时间: 2024-10-10 02:30:11

记一次线上gc调优的过程的相关文章

JVM层GC调优(上)

JVM内存结构简介(jdk1.8) JVM层的GC调优是生产环境上必不可少的一个环节,因为我们需要确定这个进程可以占用多少内存,以及设定一些参数的阀值.以此来优化项目的性能和提高可用性,而且这也是在面试中经常会被问到的问题. 想要进行GC调优,我们首先需要简单了解下JVM的内存结构,Java虚拟机的规范文档如下: https://docs.oracle.com/javase/specs/jvms/se8/html/index.html 在介绍JVM内存结构之前,我们需要先知道运行时数据区这样的一

JVM GC调优一则--增大Eden Space提高性能

缘起 线上有Tomcat升级到7.0.52版,然后有应用的JVM FullGC变频繁,在高峰期socket连接数,Cpu使用率都暴增. 思路 思路是Tomcat本身的代码应该是没有问题的,有问题的可能是应用代码升级,或者环境改变了,总之Tomcat的优先级排在最后. 先把应用的heap dump下来分析下: jmap -dump:format=b,file=path pid 用IBM的Heap Analyser分析,发现dubbo rpc调用的RpcInvocation对象和taglibs的Si

Java GC调优

当Java程序性能达不到既定目标,且其他优化手段都已经穷尽时,通常需要调整垃圾回收器来进一步提高性能,称为GC优化.但GC算法复杂,影响GC性能的参数众多,且参数调整又依赖于应用各自的特点,这些因素很大程度上增加了GC优化的难度.即便如此,GC调优也不是无章可循,仍然有一些通用的思考方法.本篇会介绍这些通用的GC优化策略和相关实践案例,主要包括如下内容: 优化前准备: 简单回顾JVM相关知识.介绍GC优化的一些通用策略.优化方法: 介绍调优的一般流程:明确优化目标→优化→跟踪优化结果.优化案例:

GC调优在Spark应用中的实践[转]

作者:仲浩   出处:<程序员>电子刊5月B 摘要:Spark立足内存计算,常常需要在内存中存放大量数据,因此也更依赖JVM的垃圾回收机制.与此同时,它也兼容批处理和流式处理,对于程序吞吐量和延迟都有较高要求,因此GC参数的调优在Spark应用实践中显得尤为重要. Spark是时下非常热门的大数据计算框架,以其卓越的性能优势.独特的架构.易用的用户接口和丰富的分析计算库,正在工业界获得越来越广泛的应用.与Hadoop.HBase生态圈的众多项目一样,Spark的运行离不开JVM的支持.由于Sp

深入JVM系列(二)之GC机制、收集器与GC调优(转)

一.回顾JVM内存分配 需要了解更多内存模式与内存分配的,请看 深入JVM系列(一)之内存模型与内存分配 1.1.内存分配: 1.对象优先在EDEN分配2.大对象直接进入老年代 3.长期存活的对象将进入老年代 4.适龄对象也可能进入老年代:动态对象年龄判断 动态对象年龄判断: 虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,当Survivor空间的相同年龄的所有对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代

Java GC 专家系列3:GC调优实践

本篇是”GC专家系列“的第三篇.在第一篇理解Java垃圾回收中我们学习了几种不同的GC算法的处理过程,GC的工作方式,新生代与老年代的区别.所以,你应该已经了解了JDK 7中的5种GC类型,以及每种GC对性能的影响. 在第二篇Java垃圾回收的监控中介绍了在真实场景中JVM是如何运行GC,如何监控GC数据以及有哪些工具可用来方便进行GC监控. 在本篇中,我将基于真实的案例来介绍一些GC调优的最佳选项.写本篇文章时,我假设你已经理解了前两篇的内容.为了深入理解本部分内容,你最好先浏览一下前两篇的内

GC 调优(实战篇) - GC参考手册

本章介绍导致GC性能问题的典型情况.相关示例都来源于生产环境, 为演示需要做了一定长度的精简. 说明: Allocation Rate, 翻译为分配速率, 而不是分配率; 因为不是百分比,而是单位时间内分配的量; 同理, Promotion Rate 翻译为 提升速率; 您应该已经阅读了前面的章节: 垃圾收集简介 - GC参考手册 Java中的垃圾收集 - GC参考手册 GC 算法(基础篇) - GC参考手册 GC 算法(实现篇) - GC参考手册 GC 调优(基础篇) - GC参考手册 GC

深入JVM系列(二)之GC机制、收集器与GC调优

一.回想JVM内存分配 须要了解很多其它内存模式与内存分配的,请看 深入JVM系列(一)之内存模型与内存分配 1.1.内存分配: 1.对象优先在EDEN分配 2.大对象直接进入老年代 3.长期存活的对象将进入老年代 4.适龄对象也可能进入老年代:动态对象年龄推断 动态对象年龄推断: 虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才干晋升到老年代,当Survivor空间的同样年龄的全部对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就能够直接进入

6. GC 调优(工具篇) - GC參考手冊

进行GC性能调优时, 须要明白了解, 当前的GC行为对系统和用户有多大的影响. 有多种监控GC的工具和方法, 本章将逐一介绍经常使用的工具. 您应该已经阅读了前面的章节: 垃圾收集简单介绍 - GC參考手冊 Java中的垃圾收集 - GC參考手冊 GC 算法(基础篇) - GC參考手冊 GC 算法(实现篇) - GC參考手冊 GC 调优(基础篇) - GC參考手冊 JVM 在程序执行的过程中, 提供了GC行为的原生数据. 那么, 我们就能够利用这些原生数据来生成各种报告.原生数据(raw dat