跳到主要内容

性能问题

https://space.bilibili.com/472502549/channel/collectiondetail?sid=1954676&spm_id_from=333.788.0.0

1、CPU 偶尔飙高,如何排查?

案例,正则表达式在进行匹配的时候可能会导致 CPU 飙高

方案1:top -H 配合 jstack 这个需要多次执行 jstack

方案2:arthas 的 thread -n 1 也要多执行几次。 jad 反编译, watch 查看入参。

2、JVM 发生 OOM 导致不断重启,如何排查?

现象:发现有 hprof 文件,但是下载一半断连了(k8s中止容器了,导致下载不完),使用 MAT 分析时,找到了大对象却定位不到代码。

案例:意外查询大量数据,内存溢出导致进程不响应,k8s kill 导致进程不断重启。

  1. jstat -gcutill $pid 1000 定位 GC 问题。
  2. ps -o cmd -C java | xargs -n1 | grep -i HeadDump
  3. ll -h 查看是否有 dump 文件。
  4. gzip -c java.hprog > java.hprof.gz 先压缩下在下载。 1.6G -> 130M
  5. MAT 内存分析工具进行分析。 dominator_tree
  • GC Root 右键 Merge shortest paths to gc roots -> with all references
  • GC root 是线程,查看线程栈 Object-> Java Basics -> thread overview and stacks
  • 展开线程栈,找接口 URL 以及查询 SQL

问题:如何 GC root 是静态变量应该如何排查?

直接在代码中查看这个静态变量在何处被修改。

x-www-form-urlencode 问题

接口联调时出现问题,尽量从网络包底层来分析

现象:接口开发者调用接口没问题,其他调用有问题。

tcpdump 抓包

base64+ 变空格

场景:对数据进行 base64 编码,然后手动拼到 url 中,在后台接收数据的时候会出现 + 变成空格的问题。

系统时不时报 GC 耗时高

GC 耗时 2 s 以上,系统流量低

  1. 分析 GC 日志 (发现有大对象分配)
  • to-sapce exhausted G1 在做 gc 时,都是先复制存活对象,再回收原 region,当没有空闲空间复制存活对象时,就会出现 to-space exhausted,而这种 GC 场景代价是非常大的。
  • allocation request: 31457294 bytes 发现大对象分配
  1. 定位大对象分配 (定时任务分配大对象)

流量低,ygc 频率低,大对象分配在老年代,用的 jdk8 的 G1 垃圾收集器。 JDK8BUG:在 ygc 不触发的情况下,一直分配大对象不会触发 G1 的 mixGC 回收。

优化GC 参数

G1 回收的过程

G1 在启动 mixed gc 之前需要先执行一个并发周期,且 mixed gc 可同时回收年轻代与老年代。

  • 并发周期:它的作用是找到那些垃圾多的 region,并确认对象存活情况。
  • mixed gc:从找到的 region 中复制走存活对象,然后回收它,由于 GC 暂停时间和回收内存量正相关,为降低暂停时间,G1 会少量多次地执行 mixed gc

-XX:InitiatingHeapOccupancyPercent 默认值从 45% -> 35% ,让并发周期启动更早 -XX:ConcGCThreads 从 1->3 , 让并发周期运行得更快 -XX:G1OldCSetRegionThresholdPercent, 10%-> 15% ,让 mixed gc 一次多回收些 region -XX:G1ReservePercent 10%->20% 增加保留空间,避免复制存活对象失败 -XX:G1HeapRegionSize 增加到 16m, 尽量避免产生大对象

定位大对象分配位置

开始收集

./profiler.sh start --all-user -e G1ColletedHeap::houmongous_obj_allocate -f ./houmongous.jfr jps 停止收集

./profiler.sh stop -f ./houmongous.jfr jps

分析 jfr 发现,定时任务调用接口返回 14m 的数据,代码中使用 String 来接收,改写成 InputStream 即可解决这个问题。

系统时不时堆内存占用高

现象:堆内存快速飙升至 80% 以上,然后快速回落。

  1. 分析 GC 日志
  2. 定位大对象 async-profile 找到分配线程栈(线程栈有可能会失真)
  3. 尝试 gdb,ebpf、perf
  4. 使用 gdb 看 Java 线程栈,(JV吗 收到 SIGQUIT信号会打印线程栈,使用 gdb 发送此信号)

JVM 内存占用高,但堆内存占用正常

  1. 检查堆与非堆
  2. 观察 native 内存块变化 (pmap查看 native 内存块的变化情况)
  3. 检查 native 内存数据

系统发布时,一波接口超时

  1. 排查日志代码,检查是否有懒加载的问题
  2. 编写采集脚本,在服务启动后自动采集 3 次 10s 的火焰图
  3. 对比火焰图。 (发现大量的类加载操作)
  4. 预热类加载