K8S集群问题——案例一:Java调用Glibc2.28-69内存分配器无法限制虚拟内存VIRT问题

一、问题描述

1、背景:

租户反馈,Apr 7 11:22容器出现夯死现象, 容器部署的单个java进程;

宿主机上,top显示的容器进程virt内存持续增长32G,目前messages日志没有看到oom的记录,基本是。租户其他bc78系统上有添加参数MALLOC_ARENA_MAX进行限制,基本维持在16G左右,目前bcoe21.10系统配置参数MALLOC_ARENA_MAX可能没有生效,导致宿主机内存碎片问题;

2、容器内部java进程的虚拟内存异常增长30g-60g

 

二、排查过程

1、租户三台测试K8S节点主机使用MALLOC_ARENA_MAX=4限制内存可分配块的创建、合并、回收,传参给容器,由于java线程占用64M内存无法及时释放,导致主内存区块被占满,后续java线程请求不断从其他内存块分配内存,导致内存持续增长,尝试解决办法:调整MALLOC_ARENA_MAX=1glibc版本;

 

ii.三节点调整MALLOC_ARENA_MAX=1

export MALLOC_ARENA_MAX=1

参考:
https://blog.csdn.net/m0_38017860/article/details/122192243

 

查看虚拟内存堆栈调用详情:pmap -x 进程ID

发现有61880KB的匿名线程的内存调用

iii.结果

两台测试机1562.28-841572.28-93,内存分配参数malloc_arena_max=1可以做到局部内存块回收,容器虚拟内存至17.3G左右,但是三台测试机均出现javacore问题;

2、测试发现glibc2.28-69glibc2.28-93glibc2.28-97内存分配器无法做到有效限制VIRT虚拟内存,尝试调整新的tcmalloct内存分配器;

i.安装方法:

tcmalloct分配器安装包:

 https://github.com/gperftools/gperftools/releases/download/gperftools-2.15/gperftools-2.15.tar.gz

 部署方案:

tar -zxvf gperftools-2.15.tar.gz
cd gperftools-2.15
./configure --enable-frame-pointers
make -j8
make install
echo /usr/local/lib > /etc/ld.so.conf.d/libtcmalloc.conf
ldconfig

编译test3.c
gcc test3.c -o test3 -lpthread -ltcmalloc

测试代码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

#define NUM_THREADS 10

void *allocate_memory(void *arg) {
    size_t *thread_id = (size_t *) arg;

    for (int i = 0; i <64*1024*1024; i+=1024){
        char* addr = (char*) malloc(1024);
    }

    // 为了让程序持续运行,这里让线程进入无限循环
    while (1) {
        sleep(1);  // 每秒休眠一次,避免无谓的 CPU 占用
    }

    return NULL;  // 实际上永远不会到达此处
}

int main() {
    pthread_t threads[NUM_THREADS];
    size_t thread_ids[NUM_THREADS];

    for (size_t i = 0; i < NUM_THREADS; ++i) {
        thread_ids[i] = i;
        if (pthread_create(&threads[i], NULL, allocate_memory, &thread_ids[i]) != 0) {
            fprintf(stderr, "Failed to create thread %zu.\n", i);
            exit(EXIT_FAILURE);
        }
    }

    // 主线程同样进入无限循环,确保程序不退出
    while (1) {
        sleep(1);  // 每秒休眠一次,避免无谓的 CPU 占用
    }

    return 0;  // 实际上永远不会到达此处
}

ii.k8s节点,调整容器java配置

涉及应用容器java配置的调整,用于调用libtcmalloct库;

iii.验证libtcmalloct内存分配器库的调用情况

 

top   #按两下e键(切换内存单位GB),按M切换内存为降序,按P切换CPU为降序,观察CPU或内存使用率较高的进程;
# VRIT:虚机内存数
# RES:物理内存数
# VRIT:RES为10:1 pmap
-x java进程id | grep ‘libtcmalloc.so‘ #抓取java线程通过内存分配器分配ARENA内存块数量级,一、4*内核数(32bit处理器);二、8*内核数(64bit处理器);

 

iv.三台测试机的调整详情

156:K8S节点主机部署TCMALLOCT内存分配器;
157:建议更换jdk版本;
158:通过yum源升级升级glibc版本;

v.结果

经过一天的观察, 调整内存分配器后,目前java容器内存稳定17.3g,期间客户有调整jdk版本,经过业务压测没有出现javacore的问题;

三、根因

这种情况是由于业务量较大,导致glibc可分配主内存空间数占满,后续申领主内存块时,导致MALLOC_ARENA_MAX 限制的可分配最大内存空间数失效;

那么按照测试参数1是否可以解释放堆顶层线程占用内存块?有明显的效果,可以做到部分内存块回收;

通过tcmalloc内存分配器,来解决后续glibc申请主内存块不足的问题,可以做到有效回收主内存块空间,维持17.3G

主要问题是由于客户的jdk版本问题,jdk版本代码存在内存泄漏导致匿名内存块的持续消耗。由于栈顶java线程申领的主内存块没有释放变为匿名内存,持续占用主内存空间,导致后续java线程没有主内存块可以申领,后续非主内存持续消耗,top中查看业务线程的虚拟内存VIRT的异常消耗(32G~60G),通过安装tcmalloct内存分配器,可以有效的限制java线程的VIRT申领维持在17.3G左右,后续客户调整jdk版本,观察容器加载业务后,目前java容器没有出现javacore的现象;

 

posted on 2024-04-24 21:39  gkhost  阅读(128)  评论(0编辑  收藏  举报

导航