生产问题(一)K8S内存溢出

2 篇文章 0 订阅

一、引言

        这是一次特殊的pre环境的内存溢出导致服务被K8S杀掉,为什么说它是特殊的,因为可以说这不是jvm的锅!

二、环境及现象

        1、pod内存分配

                设置该服务所在pod的限制为2G内存,超过2G就会达到K8S的oom界限,被K8S重启。  

        2、jvm设置

         3、现象

        服务在pre环境重启,K8S层面看下来是由于该pod使用内存过大超出限制导致的oom重启(并未被驱逐,排除被物理机OOM),但是JVM堆内并没超过限制。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAdGluZ21haWxhbmc=,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAdGluZ21haWxhbmc=,size_20,color_FFFFFF,t_70,g_se,x_16

三、排查与解决

1、堆外内存

        jvm堆内存既然没有超过限制,怀疑方向指到了堆外内存,但是排查了整个服务之后,没有使用nio进行堆外存储,第三方中间件例如rocketMq、Redisson等是基于Netty进行消息收发的,但是它们使用的nio非常少,可以忽略。

2、NativeMemoryTracking

        让运维开启了NMT追踪jvm使用的所有内存,可以看出,jvm整体使用的内存离2G还有一段距离。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAdGluZ21haWxhbmc=,size_20,color_FFFFFF,t_70,g_se,x_16

         通过pod监控可以看出在jvm使用1.6g左右内存时,pod内存达到了1.95g左右,濒临K8S的kill界限。

        通过查阅相关资料了解到在当前的容器化环境中,jvm释放的内存不会立刻被转化为物理机内存,jvm可以对这部分内存随时取用,但是它不归属于jvm,因此不会实时的触发gc。方向似乎清晰了,K8S很可能将这部分游离内存和jvm内存进行累加,达到2G限制就进行oom重启。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAdGluZ21haWxhbmc=,size_20,color_FFFFFF,t_70,g_se,x_16

3、持续观察NMT和pod节点内存变化

        有了方向就产生了新的疑问,容器这部分游离的内存随什么变化?还是毫无规律?是否是线程数量或者类加载?

        带着这些疑问,作者开始了持续两个星期的跟踪观察,但是很遗憾,从观察结果来看,游离内存变化几乎毫无规律。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAdGluZ21haWxhbmc=,size_20,color_FFFFFF,t_70,g_se,x_16

 4、解决

        其实在有了方向之后就已经可以知道解决方案了,那就是增加K8S容器可进行自由转换的游离内存空间:

                1、修改jvm的启动参数,将jvm占据的内存减少,增加K8S容器可进行自由转换的游离内存空间,但是很遗憾运维坚决不同意,因为这部分启动参数是被所有服务发布时共用的。

                2、增加pod内存,同样可以大大增加K8S容器可进行自由转换的游离内存空间,作者采用了这种方式,但是其实这有一点资源浪费。

                3、研究K8S的内存机制并且进行修改,因为实际上将游离内存计算到限制里面并不是一种很好的算法,但是作者目前对于K8S、docker的容器的了解还处于初级水平,也没有额外时间去进行研究,这个方法只能搁置到后期。

 四、总结

        看到这里,大家应该明白作者为什么说这次特殊的oom问题不在于jvm,更大的责任在于容器的内存算法。

        随着技术不断发展,问题的原因也越来越复杂,以前的oom只需要根据jvm堆栈确认服务的代码问题,现在有时候却不能把oom全部甩给jvm了。

        如果有同学遇到类似作者这样的情况,确认与jvm无关,服务又没有使用堆外内存,不妨尝试增加K8S容器可进行自由转换的游离内存空间。

五、后来

    后来作者遇到一次mmap内存分配方式问题

    也是非java内存导致的,tomcat给kill掉了,mmap属于操作系统内核层面的内存分配方式,他会预先分配内存给进程使用。4核的机器, 所以可以有32个64M的内存区域。在这个过程中加上进程使用了一部分非预先分配的内存,导致oom。

        其实和k8s那个挺像的的,很可能是同一个问题,只不过这次运维没有捕捉到K8S oom killed内核日志,所以不能确定完全一样的原因。

图片

图片

    这里采取的方案是改成jemlloc

    不少文章是说glibc默认的内存分配器ptmalloc存在内存碎片化造成堆外不断上升的现象,推荐替换为jemalloc或tcmalloc,具体步骤是:

1、下载jemalloc

2、sudo yum install -y bzip2 gcc make graphviz  安装依赖工具

tar -xvf jemalloc-5.2.1.tar.bz2 解压

3、cd jemalloc-5.2.1 && ./configure --enable-prof && make && sudo make install 安装

4、sudo -u deploy /usr/local/bin/** --version 查看版本,是否安装成功

5、到tomcat目录,/opt/tomcat/bin/**.sh 编译启动文件,增加如下配置(挂载jemalloc)

6、export PRELOAD(一个环境变量,用于指定要在程序加载时预先加载的共享库)=/usr/local/lib/libjemalloc.so

7、export MALLOC_CONF(用于配置内存分配器的行为)=prof:true,lg_prof_interval:30(设置性能分析的采样间隔为 30),lg_prof_sample:17(设置性能分析的采样率为 17),prof_prefix:/**(设置性能分析结果输出的路径前缀)

8、sudo supervisorctl restart tomcat  执行重启tomcat

9、重启完成后,java进程的内存分配器就换成jemalloc

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖当当技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值