JVM GC长暂停问题排查

JVM GC长暂停问题排查

现象

名词:GC 垃圾回收(Garbage Collection)分类 计算机科学

在高并发下,Java程序的GC问题属于很典型的一类问题,带来的影响往往会被进一步放大。不管是「GC频率过快」还是「GC耗时太长」,由于GC期间都存在Stop The World问题,因此很容易导致服务超时,引发性能问题。

事情最初是线上某应用垃圾收集出现Full GC异常的现象,应用中个别实例Full GC时间特别长,持续时间约为15~30秒,平均每2周左右触发一次;


JVM参数配置"-Xms2048M --Xmx2048M --Xmn1024M --XX:MaxPermSize=512M"

  1. 周期性长暂停

    • 线上Java应用每2周触发1次Full GC
    • 单次暂停15-30秒(正常Full GC应≤1秒)
    • 仅部分实例出现,JVM配置一致(-Xms2048M --Xmx2048M --Xmn1024M --XX:MaxPermSize=512M
  2. 异常GC特征

    • Full GC原因标记为 Ergonomics(JVM自适应策略触发)
    • GC日志未显示堆内存异常(回收前后内存占比正常)

背景

  1. 系统环境

    • 部署于 Linux虚拟机(物理内存8GB)
    • 关键参数:vm.swappiness=30(默认倾向使用SWAP)
  2. 并发场景

    • 高QPS服务:持续内存分配压力

    • 内存使用特点:

      监控项 现象
      物理内存 未完全耗尽(free显示可用内存充足)
      SWAP分区 占用305MB(进程检测)
      CPU Full GC时骤增
  3. 矛盾点

    • JVM认为内存充足(堆配置仅2GB),但OS将部分内存页换出到SWAP磁盘
    • 低频率Full GC(2周1次)→ 内存页在SWAP停留时间长 → GC遍历时触发磁盘换入

核心冲突示意图

复制代码
JVM Full GC  
│  
├─ 需遍历堆内存  
│     │  
│     ├─ 内存页在物理内存 → 微秒级访问  
│     │  
│     └─ 内存页在SWAP磁盘 → 毫秒级磁盘I/O(**10^3倍延迟**)  
│  
└─ 大量页换入操作阻塞GC线程 → **STW时间膨胀至秒级**  

关键结论:低频Full GC + SWAP换出 + 遍历换入需求 = 长暂停灾难链


第一层:核心概念定义
  1. JVM与GC机制

    • Java虚拟机(JVM)通过垃圾回收(GC)管理内存,GC执行时会触发"Stop The World"(STW)暂停
    • Full GC:清理整个堆内存的回收操作,耗时显著
  2. 问题现象

    • 某应用实例周期性出现15-30秒Full GC
    • JVM配置:-Xms2048M --Xmx2048M --Xmn1024M --XX:MaxPermSize=512M
    • Full GC诱因:Ergonomics(自适应策略触发)

第二层:系统级分析
  1. GC日志与服务器指标关联

    • GC日志显示回收前后堆内存无异常
    • 监控发现Full GC时点出现:
      • CPU使用率骤增(图1红框)
      • 物理内存增长拐点 + SWAP区释放(图1橙框)
  2. SWAP机制验证

    shell 复制代码
    # 检测进程SWAP占用
    for i in $(cd /proc; ls | grep "^[0-9]" | awk '$0>100'); do 
      awk '/Swap/{a=a+$2} END{print '"$i"',a/1024"M"}' /proc/$i/smaps 2>/dev/null
    done | sort -k2nr | head -10
    • 目标进程占用SWAP 305MB
    • 异常实例swappiness=30(倾向使用SWAP),正常实例swappiness=0

第三层:根因分析
  1. SWAP与GC的致命交互

    • Linux内存压力时:物理内存页换出到SWAP(swap out)
    • Full GC遍历堆内存时:SWAP数据换回物理内存(swap in)
    • 磁盘I/O操作导致GC遍历耗时剧增(从毫秒级→秒级)
  2. 对比实验佐证

    场景 SWAP用量 Full GC耗时 关键差异
    问题实例(2周1次) 305MB 15-30秒 内存页被换出至SWAP
    实名服务(几小时1次) 54MB 576ms SWAP无活动+频繁GC避免换出

第四层:解决方案
  1. 临时措施

    shell 复制代码
    sysctl vm.swappiness=0  # 禁用SWAP倾向
    swapoff -a              # 关闭SWAP分区(需确保物理内存≥SWAP用量)
  2. 永久配置

    vi 复制代码
    vm.swappiness=0
  3. 效果验证

    • 关闭SWAP后Full GC耗时降至190ms(图2橙框)

基座:延伸思考
  1. 关键疑问解答

    • Q:SWAP是否必然导致GC卡顿?
      A:否!仅当GC时发生swap in操作才会引发(实名服务证明)
    • Q:JVM为何不禁用SWAP?
      A:Linux内存管理需平衡安全性与性能(kswapd守护进程机制)
  2. 最佳实践

    • 高并发服务建议:vm.swappiness=0 + 足够物理内存
    • 备选方案:降低堆大小(避免内存换出)
  3. 核心结论复述
    SWAP与GC同时触发→内存页换入换出→磁盘I/O阻塞STW→秒级卡顿,通过禁用SWAP或优化内存配置可根治。

原文参考https://blog.csdn.net/cnzzs/article/details/141273193

相关推荐
J***51685 分钟前
SpringSecurity的配置
java
面汤放盐7 分钟前
软件架构指南 Software Architecture Guide
java·微服务·devops
tkevinjd7 分钟前
JUC5(线程池)
java·线程池·多线程·juc
Tao____8 分钟前
如何对接Modbus-tcp协议(使用Thinlinks物联网平台)
java·物联网·网络协议·tcp/ip·modbus
鱼跃鹰飞12 分钟前
经典面试题:K8S的自动缩扩容和崩溃恢复
java·容器·kubernetes
Coder_Boy_16 分钟前
Spring Boot 事务回滚异常 UnexpectedRollbackException 详解(常见问题集合)
java·spring boot·后端
青云交18 分钟前
Java 大视界 -- 基于 Java+Redis Cluster 构建分布式缓存系统:实战与一致性保障(444)
java·redis·缓存·缓存穿透·分布式缓存·一致性保障·java+redis clus
不知疲倦的仄仄19 分钟前
第五天:深度解密 Netty ByteBuf:高性能 IO 的基石
java·开源·github
xiaobaishuoAI22 分钟前
后端工程化实战指南:从规范到自动化,打造高效协作体系
java·大数据·运维·人工智能·maven·devops·geo
期待のcode25 分钟前
TransactionManager
java·开发语言·spring boot