一次性能调优过程记录

问题描述

重复点击页面导致页面请求变得超级慢

问题解决过程

1 去服务器top指令查看消耗CPU和内存最大的进程,根据对系统的了解,找到几个可能出问题的进程
(由于我们的测试服务都是部署在同一台机器上面的只能猜测)这里猜测可能是rlc

2 top -p 进程id (指定查看该进程的详情) 后按 H 查看线程信息,找出消耗CPU和内存最大的进程

3 将线程id 转换成 16进制

4 jstack 进程id | grep -A10 线程id(16进制) // 查看该线程的堆栈信息 -A10的意思是显示过滤字段后10行的信息

5 查看到这个线程竟然是GC线程 (居然不是我自己的代码)

6 jstat -gc pid 1000 10
查看GC情况,得到的结果是YGC与FGC快速同频增长,但是回收内存却非常少.
分析:同频增长,可能有两个原因:
1在进行YGC的时候,空间分配担保失败,导致需要进行FGC;
2在YGC之后,动态年龄判断,老年代内存空间不足,导致触发FGC.
以上无论那种情况,都指向老年代内存空间不足.
但是现象是每次FGC回收的空间非常少,而我们系统中常驻内存的对象并不多(基于对系统的理解),所以判断是一个请求链路过程中所产生的对象,太多或者太大导致GC过于频繁,而且回收效率并不高.
由于是老年代空间不足,所以猜测可能是一个请求过程中,所产生的大对象直接进入老年代,在请求完成前,这些对象不能被回收,导致内存空间紧缺频繁GC,进而导致系统变卡.

7 dump出内存中的信息 jmap -dump:format=b,file=10010.dump 进程id

8 用eclipse memory analyzer分析dump文件,
发现消耗内存最大的是byte数组,其实这个也正常,不正常的是这些byte数组的长度都相同且长度都是10M,
byte数组的内容都是http请求头的信息,例如(POST /rlc/appService/query…),我尝试将这个数组中的数据复制的文件中,发现其实际内容长度远小于10M.
因此怀疑是tomcat为每个请求头固定分配了10M的空间,网上查了写资料发现springboot能修改tomcat默认给请求头分配的内存空间大小(server.max-http-header-size)
果然我们系统不知道谁配置了个10M,蓝瘦香菇.删除此配置(默认是8K)重新测试,没得问题.

9 然而后续来了,在后续使用过程中发现,feign调用的时候会报错,说请求头长度不够用了,查看代码,feign的参数中很多list使用的注解是@RequestParam,
但是声明的请求类型是post,以此可以判断,feign在组装请求的时候,会将@RequestParam直接拼接到url后面,并不会管你是不是post请求,
所以我们将list的参数修改成@RequestBody,保证其封装到请求体中,重新发布测试,没得问题,美滋滋!

思维拓展

如果在第5步中线程堆栈显示的是我们自己写的代码,就应该具体看我们写的代码是否有问题,可能是占用内存过多,或者占用CPU过高,但并不是说,一定有问题,
因为有些情况下,需要计算或者占用的内存必须很高,也是有可能的.

如果在第6步发现,YGC与FGC快速同频增长,但是老年代回收的内存很多,这就要考虑两个问题:
1空间分配担保失败导致的,这种情况下,一般没什么特别的建议,只能加大内存空间,或者优化代码,减少内存的使用.
2动态年龄判断导致的,这表示每次请求过程中产生的对象所占空间比较大,可以适当调大年轻代的大小(或者surivor区的大小)
以上情况下,也可以适当调整进入老年代的年龄,使常驻对象尽早的进入老年代

第9步中feign在组装请求时,并不管是不是post请求,@RequestParam参数直接拼在url后面,@RequestBody放在请求体中.
这与HTTP协议明显不符,说明协议仅仅只是协议,具体实际情况可以不按照协议标准来(最起码Feign就不管协议).
另外,以前我们听说过get请求url最长2K的限制,好像在这里也不顶用,这里url的长度明显是由服务端能接收的长度决定的(server.max-http-header-size).
网上查了一下,url长度2K的限制是浏览器自行规定的.由此可见,协议仅仅只是协议,尊不遵守看实现者心情.

知识巩固

对象进入老年代的时机

  1. 大对象直接进入老年代
  2. 常驻对象进入老年代,年龄大于15的
  3. 动态年龄判断

对象动态年龄判断

当前放对象的Survivor区域中(其中一块区域,放对象的那块)一批对象的总大小大于这块Survivor内存大小的50%(-XX:TargetSurvivorRatio),
那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代。例如:Survivor里现有一批对象,年龄1+年龄2+年龄n的多个年龄对象总和超过了
Survivor区域的50%,此时就会把年龄n及其以上的对象都放入老年代,这个规则其实是希望那些可能长期存活的对象,尽快的进入老年代,对象动态
年龄判断机制一般是在minor GC之后触发的

老年代空间分配担保机制

年轻代每次minorGC之前JVM都会计算下老年代升级可用的空间大小,如果这个空间大小小于年轻代里现在所有的对象大小的总和(包括垃圾对象)
此时就会触发空间分配担保机制(可用-XX:-HandlePromotionFailure)即判断来年代的可用内存大小,是否大于之前每一次minorGC进入老年代的对象的
平均大小。如果上一步结果是小于或者没有开启空间分配担保机制,那么就会触发一次FullGC,对老年代和年轻代一起回收一次垃圾。如果回收完还是没有
足够噗空间存放新的对象就会发生OOM。

当然如果MinorGC之后剩余存活的需要挪动到老年代的对象大小还是大于老年代的可用空间,那么也会触发FullGC,FullGC完之后如果还是没有空间放minorGC
之后的存活对象,也会发生OOM

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值