Posted in

Go程序被kill前最后1秒发生了什么?用eBPF kprobe+ringbuf重建OOM触发前500ms全栈调用链

第一章:Go程序被系统杀死的典型场景与现象观察

当Go程序在Linux系统中突然终止且无panic堆栈输出时,往往并非代码逻辑错误所致,而是被操作系统主动终止。最典型的信号是SIGKILL(信号9),它不可捕获、不可忽略,进程会立即终止——此时dmesg日志中常出现Out of memory: Kill process字样,表明OOM Killer介入。

内存耗尽触发OOM Killer

Linux内核在物理内存和交换空间不足时,会根据oom_score_adj值选择“牺牲者”。可通过以下命令快速定位近期被杀的Go进程:

# 查看内核OOM事件记录
dmesg -T | grep -i "killed process" | tail -5

# 查看指定Go进程的OOM评分(值越高越易被杀)
cat /proc/$(pgrep -f "myapp")/oom_score_adj  # 正常为0,若设为1000则极易被选中

资源限制导致的静默终止

容器或systemd环境中常配置MemoryLimit或cgroup v2内存上限。超出后进程被cgroup直接kill,不经过OOM Killer路径:

# 检查cgroup内存限制是否触发(适用于cgroup v2)
cat /sys/fs/cgroup/myapp/memory.events | grep "oom\|oom_kill"
# 输出示例:oom 1, oom_kill 1 → 表明已发生1次OOM kill

系统级超时与守护进程管理

systemd服务若未正确设置RestartSec或StartLimitIntervalSec,在频繁崩溃后可能被StartLimitBurst拦截;同时TimeoutStopSec过短会导致SIGTERM未处理完即发SIGKILL。关键配置示例: 配置项 推荐值 说明
MemoryMax 2G 显式限制内存上限,避免触发全局OOM
Restart=on-failure — 仅在非0退出码时重启,排除SIGKILL场景
KillMode=control-group — 确保整个cgroup被清理,而非仅主进程

异常信号与调试线索

除OOM外,SIGABRT(来自runtime/debug.SetTraceback("crash"))或SIGUSR1(pprof HTTP服务关闭)也可能被误判为“被杀”。验证方式:

# 检查进程最后状态(需启用coredump)
sysctl kernel.core_pattern  # 确认core文件生成路径
journalctl -u myapp.service --since "1 hour ago" | grep -E "(signal|exit code)"

持续监控应结合/proc/PID/status中的State(R/S/Z)、VmRSS及SigQ(未决信号数)字段,而非仅依赖应用日志。

第二章:Linux内核OOM Killer机制深度解析

2.1 OOM Killer触发条件与评分算法源码级剖析

OOM Killer并非随机选择进程终结,而是在内核内存严重不足时,由try_to_free_mem_cgroup_pages()失败后,经out_of_memory()路径触发。

触发阈值判定逻辑

核心条件包括:

  • pages_min(zone最低水位)被突破且无法通过直接回收满足
  • 所有内存域均处于ZONE_OOM状态
  • sysctl_oom_kill_allocating_task未强制启用时,需遍历所有可杀进程

评分核心函数:oom_badness()

unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,
                          const nodemask_t *nodemask, unsigned long totalpages)
{
    long points = 0;
    // 基础内存占用:RSS + Swap,按页计
    points = get_mm_rss(p->mm) + get_mm_swap_usage(p->mm);
    // 按CPU权重衰减:越老进程得分越低(避免频繁杀长期服务)
    points -= (p->signal->oom_score_adj * totalpages) / 1000;
    return points > 0 ? points : 0;
}

oom_score_adj范围为[-1000, 1000],-1000表示免疫;totalpages为系统总可用页数,用于归一化。

评分权重影响因子表

因子 权重方向 说明
RSS + Swap 正向 占用物理内存越多,越易被选中
oom_score_adj 负向 用户可调,如echo -1000 > /proc/PID/oom_score_adj
进程年龄 隐式衰减 通过task_struct->start_time间接影响调度优先级

graph TD A[内存分配失败] –> B{是否满足OOM触发条件?} B –>|是| C[out_of_memory()] C –> D[遍历task_list] D –> E[调用oom_badness计算得分] E –> F[选取得分最高且非init的进程] F –> G[发送SIGKILL]

2.2 cgroup v1/v2内存子系统对Go程序OOM判定的影响实践验证

Go runtime 内存观测关键指标

Go 程序的 runtime.ReadMemStats() 返回的 HeapSys 和 TotalAlloc 受 cgroup memory limit 实时约束,但 GOGC 触发阈值仍基于 HeapLive(即 HeapAlloc - HeapReleased),不直接感知 cgroup hard limit。

cgroup v1 vs v2 的 OOM 触发差异

维度 cgroup v1 (memory) cgroup v2 (memory)
OOM 信号源 kernel memory subsystem unified memory.oom.group + memory.events
Go 感知延迟 高(依赖 memcg->oom_kill_disable) 低(memory.events 中 oom 计数器实时更新)
runtime/debug.SetMemoryLimit 兼容性 ❌ 不识别 cgroup limit ✅ 自动适配 memory.max(Go 1.22+)

实验验证:强制触发对比

# 启动受限容器(v2)
docker run --rm -m 100M -it golang:1.22-alpine sh -c '
  cat /sys/fs/cgroup/memory.max  # 输出: 104857600
  go run -gcflags="-l" <(echo "
  package main
  import (\"runtime\"; \"time\")
  func main() {
    buf := make([]byte, 90*1024*1024) // 接近 limit
    runtime.GC()
    time.Sleep(time.Second)
  }")
'

逻辑分析:该代码分配 90MB 堆内存,在 cgroup v2 下,Go 1.22+ 会读取 memory.max 并将 runtime/debug.SetMemoryLimit(100<<20) 设为 GC 目标上限;而 v1 环境下 GOGC 仅依据 HeapLive,常导致内核 OOM Killer 在 HeapSys > 100MB 时直接杀进程,无 GC 救援机会。

OOM 判定路径差异(mermaid)

graph TD
  A[Go 分配内存] --> B{cgroup v1?}
  B -->|是| C[内核 memcg OOM → 直接 kill]
  B -->|否| D[读 memory.max → SetMemoryLimit]
  D --> E[GC 在 memory.max * 0.95 前主动回收]
  E --> F[避免内核 OOM]

2.3 Go runtime内存分配路径与内核page fault交互的eBPF观测实验

Go程序在首次访问新分配的堆内存时,会触发内核page fault——此时虚拟页尚未映射物理页,由do_page_fault处理。我们使用eBPF追踪这一关键交点。

观测目标定位

  • go:runtime.mallocgc(Go分配入口)
  • kprobe:do_page_fault(内核缺页处理)
  • tracepoint:mm/page-fault(标准化事件源)

核心eBPF代码片段

// bpf_prog.c:关联Go分配与缺页事件
SEC("tracepoint/mm/page-fault")
int trace_page_fault(struct trace_event_raw_page_fault *ctx) {
    u64 addr = ctx->address;           // 触发缺页的虚拟地址
    u32 pid = bpf_get_current_pid_tgid() >> 32;
    bpf_map_update_elem(&fault_map, &pid, &addr, BPF_ANY);
    return 0;
}

该程序捕获缺页地址并按PID索引,便于与用户态Go分配栈帧关联;fault_map为BPF_MAP_TYPE_HASH,超时设为5s防内存泄漏。

关键观测维度对比

维度 mallocgc调用点 page-fault触发点 延迟(μs)
首次分配(1MB) mheap.allocSpan handle_mm_fault 8.2–15.7
再次访问已分配页 — 无触发 —
graph TD
    A[Go mallocgc] --> B[申请虚拟地址空间]
    B --> C{是否已映射物理页?}
    C -->|否| D[触发page-fault]
    C -->|是| E[直接访问]
    D --> F[do_page_fault → alloc_pages]
    F --> G[建立PTE映射]

此路径揭示Go runtime“懒映射”设计与内核内存管理的协同本质。

2.4 /proc/PID/status与/proc/PID/oom_score_adj在Kill前的动态变化追踪

当内核OOM Killer启动决策流程时,/proc/PID/status 中的 VmRSS 和 MMUPageSize 字段实时反映内存压力,而 /proc/PID/oom_score_adj 则被动态调整以影响优先级。

数据同步机制

OOM子系统在 select_bad_process() 前调用 oom_evaluate_task(),触发以下原子更新:

# 示例:监控某进程在OOM触发前1秒内的变化
watch -n 0.1 'echo -n "oom_score_adj: "; cat /proc/1234/oom_score_adj; \
              echo -n "VmRSS: "; grep VmRSS /proc/1234/status'

逻辑分析:watch 以100ms粒度轮询,oom_score_adj 取值范围为[-1000,1000],-1000表示免疫,0为基准;VmRSS 单位为KB,其突增常 precede OOM kill。

关键字段对照表

字段 来源文件 更新时机 语义
oom_score_adj /proc/PID/oom_score_adj mem_cgroup_oom_vote() 调用链中 用户可写,影响 oom_badness() 计算权重
VmRSS /proc/PID/status mm/vmscan.c 回收路径中更新 实际物理内存占用,直接参与 badness 分数计算

决策流程示意

graph TD
    A[OOM detected] --> B[遍历task_struct链表]
    B --> C[读取/proc/PID/oom_score_adj]
    B --> D[读取/proc/PID/status中的VmRSS]
    C & D --> E[计算oom_badness分数]
    E --> F[选择最高分进程kill]

2.5 模拟OOM压力下Go程序RSS/VSS突增时序与kill信号投递延迟测量

实验环境构造

使用 stress-ng --vm 1 --vm-bytes 8G --timeout 30s 触发系统内存压力,同时通过 /proc/[pid]/statm 定期采样 RSS/VSS(单位:pages):

# 每10ms采样一次,持续60秒
awk '{print $1,$2}' /proc/$(pgrep -f "go run main.go")/statm | \
  tee /tmp/mem_trace.log

awk '{print $1,$2}' 提取第1列(VSS)、第2列(RSS);pgrep -f 精确匹配Go主进程;高频采样需避免/proc遍历开销。

OOM killer信号投递延迟捕获

启用内核日志过滤并关联时间戳:

事件类型 时间戳(ns) 进程PID 备注
oom_kill 171234567890 12345 kernel log
SIGKILL recv 171234567923 12345 strace -e trace=kill

时序对齐分析

// Go侧注册信号监听(仅用于验证接收时刻)
signal.Notify(c, syscall.SIGKILL)
select {
case <-c: // 记录纳秒级接收时间
}

syscall.SIGKILL 无法被阻塞或忽略,但用户态接收存在调度延迟;select 阻塞直到信号送达运行时队列。

延迟分布特征

graph TD
A[OOM触发] --> B[内核选择victim]
B --> C[发送SIGKILL到task_struct]
C --> D[调度器唤醒目标goroutine]
D --> E[Go runtime分发至channel]
E --> F[用户代码收到信号]

实测显示:从oom_kill日志到Go signal.Notify接收平均延迟为23–117ms,标准差±31ms。

第三章:eBPF kprobe探针设计与Go运行时关键钩子选择

3.1 runtime.mallocgc、runtime.sysAlloc、runtime.scavengeHeap等函数的kprobe插桩实践

插桩目标函数选择依据

  • runtime.mallocgc:用户态堆分配主路径,触发GC标记与内存复用逻辑
  • runtime.sysAlloc:底层向OS申请大块内存(≥64KB),绕过mcache与mheap缓存
  • runtime.scavengeHeap:Go 1.21+ 引入的后台内存归还机制,周期性释放未使用页

kprobe脚本核心片段

# 基于bpftrace插桩示例
kprobe:runtime.mallocgc {
    @size[comm] = hist(arg0);  // arg0为申请字节数
}
kprobe:runtime.sysAlloc {
    printf("sysAlloc %d bytes by %s\n", arg1, comm);  // arg1为size参数
}

arg0在mallocgc中表示请求大小(含对齐开销);arg1在sysAlloc中为原始请求尺寸,不包含页对齐填充。

关键参数对照表

函数 参数索引 含义 典型值范围
mallocgc arg0 分配字节数(含header) 8 ~ 32MB
sysAlloc arg1 原始请求大小 ≥65536
scavengeHeap arg0 scavenge目标页数 0 ~ 1024

内存回收路径示意

graph TD
    A[scavengeHeap] --> B{是否满足scavenging阈值?}
    B -->|是| C[调用 sysFree]
    B -->|否| D[延迟下次触发]
    C --> E[归还至OS via madvise]

3.2 Go 1.21+ MMAP/THP行为变更对eBPF观测点适配性验证

Go 1.21 起默认启用 MAP_HUGETLB 兼容路径,并调整 mmap 的匿名映射策略,影响 eBPF 程序在用户态内存映射区域(如 ringbuf、perf event array)的可观测性。

THP 与 eBPF 探针冲突表现

  • 内核 thp_split_page() 可能触发页分裂,导致 bpf_probe_read_kernel() 读取不一致;
  • Go 运行时在 runtime.malg 中分配栈时启用 MADV_HUGEPAGE,干扰 kprobe 对 mmap 系统调用入口的精确捕获。

关键修复验证代码

// 启用显式非大页映射,绕过 THP 干扰
mem, err := unix.Mmap(-1, 0, 4096,
    unix.PROT_READ|unix.PROT_WRITE,
    unix.MAP_PRIVATE|unix.MAP_ANONYMOUS|unix.MAP_NORESERVE,
    0)
if err != nil {
    panic(err) // MAP_NORESERVE 避免 THP 合并
}

MAP_NORESERVE 禁用内核预分配大页,确保 eBPF tracepoint:syscalls:sys_enter_mmap 观测到原始映射标志;MAP_ANONYMOUS 配合 MADV_NOHUGEPAGE 可进一步隔离 THP。

行为对比表

场景 Go ≤1.20 Go ≥1.21(默认)
mmap(..., MAP_ANON) 不触发 THP 可能触发 khugepaged 合并
eBPF uprobe 定位精度 高(固定页边界) 降低(页迁移/分裂)
graph TD
    A[Go 1.21 mmap 调用] --> B{是否含 MAP_HUGETLB?}
    B -->|否| C[内核尝试 THP 合并]
    B -->|是| D[绕过 THP 路径]
    C --> E[eBPF kprobe 地址偏移漂移]
    D --> F[稳定观测点]

3.3 ringbuf高性能事件缓冲区在毫秒级采样中的可靠性压测

毫秒级采样挑战

高频传感器/追踪点每1ms生成事件,传统锁队列在10k+ EPS下出现丢包与延迟毛刺。ringbuf凭借无锁、缓存友好、原子指针推进特性成为首选。

数据同步机制

使用__atomic_load_n与__atomic_store_n保障生产者/消费者指针的内存序一致性:

// 生产者提交:先写数据,再推进tail
__atomic_store_n(&rb->tail, new_tail, __ATOMIC_RELEASE);
// 消费者获取:先读tail,再按head-tail差值批量消费
size_t avail = __atomic_load_n(&rb->tail, __ATOMIC_ACQUIRE) - head;

__ATOMIC_RELEASE确保数据写入对消费者可见;__ATOMIC_ACQUIRE防止指令重排导致读到脏head。

压测关键指标对比(10ms窗口)

场景 丢包率 P99延迟 吞吐量
lock-based Q 2.7% 4.2ms 8.3k EPS
ringbuf (SPSC) 0.0% 0.18ms 42.1k EPS

故障注入流程

graph TD
    A[注入随机CPU抢占] --> B{ringbuf满载检测}
    B -->|true| C[触发drop_count计数]
    B -->|false| D[持续memcpy消费]
    C --> E[上报metric: ringbuf_drop_total]

第四章:全栈调用链重建与500ms临界窗口分析方法论

4.1 基于bpf_get_stackid()与Go符号表解析的用户态栈回溯实战

Go 程序因 Goroutine 调度和栈分裂机制,传统 libunwind 在 eBPF 中无法直接解析用户态栈。需结合 bpf_get_stackid() 获取紧凑栈索引,并联动 Go 运行时符号表完成语义还原。

核心流程

  • bpf_get_stackid(ctx, &stack_map, BPF_F_USER_STACK) 提取用户栈哈希 ID
  • 通过 /proc/PID/exe + readelf -w 提取 .gosymtab 和 .gopclntab 段
  • 利用 go tool objdump -s "main\." 验证符号偏移映射关系

符号解析关键字段

字段 说明 示例值
funcnametab 函数名字符串池起始地址 0x12345678
pclntab PC→函数/行号映射表 0x87654321
nfunctab 函数条目总数 1248
u64 stack_id = bpf_get_stackid(ctx, &stacks, BPF_F_USER_STACK);
if (stack_id < 0) return 0;
bpf_map_update_elem(&stack_traces, &pid, &stack_id, BPF_ANY);

BPF_F_USER_STACK 强制仅采集用户态帧;&stacks 是 BPF_MAP_TYPE_STACK_TRACE 类型 map,用于后续 bpf_get_stack() 批量读取原始地址;负返回值表示栈过深或无符号信息。

栈帧还原流程

graph TD
    A[bpf_get_stackid] --> B[获取栈哈希ID]
    B --> C[查stack_traces map]
    C --> D[调用bpf_get_stack]
    D --> E[地址数组 → Go pclntab 解析]
    E --> F[输出函数名+行号]

4.2 内核态(mm/oom_kill.c)与用户态(runtime/proc.go)事件时间对齐技术

数据同步机制

内核 OOM killer 触发时,mm/oom_kill.c 中调用 dump_tasks() 记录时间戳(jiffies),而 Go 运行时在 runtime/proc.go 的 sysmon 监控中使用 nanotime() 获取单调时钟。二者时基不同,需对齐。

时间映射表

事件源 时间基准 精度 同步方式
oom_kill.c jiffies ~10ms 通过 get_monotonic_boottime() 转换
proc.go nanotime ~1ns 绑定 CLOCK_MONOTONIC
// mm/oom_kill.c 片段:记录触发时刻(需转换为纳秒)
ktime_t oom_trigger_time = ktime_get_boottime(); // 替代 jiffies,统一时基

该调用返回 ktime_t(纳秒级 boottime),避免 jiffies 依赖 HZ 配置带来的漂移,为跨态对齐提供统一锚点。

对齐流程

graph TD
A[OOM触发] --> B[ktime_get_boottime ns]
B --> C[写入/proc/PID/oom_score_adj]
C --> D[Go runtime 读取并比对 nanotime]
D --> E[计算Δt < 50ms则视为同事件]
  • 所有对齐逻辑基于 CLOCK_BOOTTIME;
  • 用户态通过 syscall.Syscall(SYS_clock_gettime, ...) 主动同步内核时钟快照。

4.3 Go GC STW阶段与OOM触发的竞态关系可视化建模

竞态核心:内存分配与GC暂停的时序冲突

当堆内存接近 GOGC 阈值时,GC 启动标记准备(mark assist);若此时突发大量分配,可能在 STW 前触发 OS OOM Killer —— 因为 runtime 尚未完成内存回收,而 RSS 已超 cgroup limit。

关键观测点对比

指标 STW 开始前 STW 执行中
分配器可用 heap ≈ 100MB(临界) 冻结,无新分配
RSS(RSS) 980MB(cgroup=1GB) 995MB(OOM风险峰值)
goroutine 状态 大量 runnable 全部 stopped

Mermaid 时序建模

graph TD
    A[Alloc 10MB/s] --> B{heap ≥ GOGC?}
    B -->|Yes| C[Start GC mark assist]
    C --> D[Alloc spikes → RSS↑]
    D --> E[OS OOM Killer signal]
    C --> F[Enter STW]
    F -->|delayed| G[OOM already fired]

典型复现场景代码

func triggerRace() {
    // 模拟 STW 前最后窗口的分配洪流
    for i := 0; i < 1000; i++ {
        _ = make([]byte, 2<<20) // 2MB slice each
        runtime.Gosched()       // 诱使调度器延迟 STW 进入
    }
}

逻辑分析:runtime.Gosched() 主动让出 P,延长 mark assist 阶段,使 RSS 在 STW 实际冻结前持续攀升;2<<20 确保单次分配跨越 span 边界,加剧内存碎片与 RSS 增长非线性。参数 2<<20 对应 2MB,是触发 mheap.allocSpan 的典型阈值,易引发额外元数据开销。

4.4 从ringbuf原始数据到火焰图+时序图的自动化链路还原Pipeline

数据采集与结构化解析

eBPF程序将内核事件写入ringbuf,每条记录含时间戳、PID、栈帧地址及自定义元数据。解析器需按bpf_ringbuf_output协议对齐字节流:

// ringbuf record layout (v5.15+)
struct event_t {
  __u64 ts;        // nanosecond monotonic clock
  __u32 pid;       // process ID (not tid)
  __u16 stack_len; // number of frames
  __u8  stack[STACK_MAX * sizeof(__u64)];
};

ts用于跨节点时钟对齐;stack_len决定后续libunwind符号化解析深度;stack[]为紧凑存储的地址数组,避免指针间接引用。

多模态可视化流水线

graph TD A[ringbuf raw bytes] –> B[parse + symbolize] B –> C[FlameGraph: folded stack] B –> D[Timeline: PID/TS/Duration]

组件 输出格式 关键依赖
stackcollapse-bpf.pl folded text bpf_get_stackid()
flamegraph.pl SVG Perl + d3-flamegraph
trace-event-timeline JSON/HTML perf script -F time,pid,comm

实时链路关联

  • 按pid + ts聚合生成调用链(Span ID推导)
  • 利用bpf_get_current_task()获取父进程信息,补全上下文
  • 支持--delta模式自动对齐用户态/内核态耗时断点

第五章:防御性编程与OOM韧性增强的最佳实践

内存边界校验与预分配策略

在高并发订单系统中,曾因未限制用户上传的Excel解析行数导致OOM:单次请求解析超20万行时,ArrayList动态扩容引发连续内存碎片与Full GC风暴。修复方案采用双重防护——解析前通过Workbook.getNumberOfSheets()和Sheet.getLastRowNum()预估总单元格数,若超过阈值(如50万)则直接返回413 Payload Too Large;同时初始化ArrayList时指定容量:new ArrayList<>(Math.min(expectedSize, 10000)),避免多次Arrays.copyOf()触发的冗余内存拷贝。

异常安全的资源释放模式

Java 8+ 必须使用try-with-resources语法处理InputStream、ResultSet等可关闭资源。某支付对账服务曾因手动finally{conn.close()}未判空,当conn为null时抛出NullPointerException掩盖了原始的SQLException,导致连接池耗尽后无法及时发现。正确写法示例如下:

try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement("SELECT * FROM tx WHERE ts > ?")) {
    ps.setTimestamp(1, lastCheckPoint);
    try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) { /* 处理逻辑 */ }
    }
}

基于指标驱动的熔断降级机制

部署Prometheus监控JVM堆内存使用率(jvm_memory_used_bytes{area="heap"})与GC时间占比(jvm_gc_pause_seconds_sum / jvm_uptime_seconds)。当堆使用率持续5分钟>90%且Young GC频率>20次/分钟时,自动触发Sentinel规则: 触发条件 降级动作 生效范围
heap_usage > 0.9 && gc_time_ratio > 0.15 熔断orderService.submit()方法 全集群节点
old_gen_usage > 0.85 限流reportService.export()至QPS=5 单机实例

堆外内存泄漏的定位与规避

Netty应用出现OutOfDirectMemoryError但堆内存充足。通过-XX:NativeMemoryTracking=detail启动后执行jcmd <pid> VM.native_memory summary scale=MB,发现Internal区域持续增长。根因是未调用ByteBuf.release()导致PooledByteBufAllocator的内存池泄漏。修复后强制要求所有ChannelHandler实现ChannelInboundHandlerAdapter并重写channelRead():

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    if (msg instanceof ByteBuf) {
        try {
            // 处理业务逻辑
        } finally {
            ((ByteBuf) msg).release(); // 必须释放
        }
    }
}

容器化环境下的内存配置协同

Kubernetes中设置resources.limits.memory: 2Gi但JVM未配置-XX:+UseContainerSupport,导致HotSpot误读宿主机内存(64GB)而设置过大MaxHeapSize(默认1/4),实际容器内存不足时被OOM Killer杀死。必须组合配置:

env:
- name: JAVA_TOOL_OPTIONS
  value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=50.0"

配合-XX:+PrintGCDetails -Xlog:gc*:file=/var/log/gc.log:time,tags:filecount=5,filesize=50M实现GC日志滚动归档。

压测验证闭环流程

使用JMeter模拟1000并发用户持续压测30分钟,监控指标包括:

  • jvm_memory_committed_bytes{area="heap"}曲线是否平缓
  • process_resident_memory_bytes峰值不超过容器limit的85%
  • jvm_gc_pause_seconds_count{action="end of major GC"}增量≤3次
    任一指标异常即回滚JVM参数并重新分析MAT直方图中的char[]、byte[]对象占比。

在 Kubernetes 和微服务中成长,每天进步一点点。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注