背景知识 (background)

老年代的对象大多是经过多轮Young GC后晋升上来的,即对象在堆里存活的时间比较长。

老年代内存不足通常意味着内存泄露,伴随着频繁的FullGC,可能会有较大停顿,甚至停顿十几秒,导致健康检查失败或接口超时。

目前老年代内存占用超过80%且持续2分钟,则会触发报警。

查看指标 (dashboard)

在这里插入图片描述

其中,Old Gen(heap)即老年代内存占用 ,min 即所选时段最小值,max 为所选时段最大占用,current为当前内存。

另外,开发也应关注监控面板:GC耗时 和 GC原因与频次。

止损措施 (action)

若想保存案发现场,可临时摘流后dump内存。

若想快速恢复,去私有云/容器平台手动重启报警节点,一般重启会平滑摘流、优雅停机。

事后改进(postmortem)

老年代内存不足,大多是内存泄露,建议摘流后Dump堆内存,联系OP下载到本地MAT内存分析,及时修复。

我们整理了一些常见的原因(cause),仅供参考。

可能的原因 (cause)

内存泄露的原因很多,大多是对象的生命周期太久了。

一、对象被长生命周期的组件引用

常见的全局静态变量、单例对象等强引用临时变量。

比如,Map每次调用Put添加对象,但缺乏淘汰机制;本地缓存没有限制最大缓存数量;ThreadLocal没有正确的清理数据;一次性加载过多数据到内存里;

在性能统计等场景使用Map时,一定要确保在任意编码习惯下的 Key / Value内存开销是可控的,比如,Druid 数据源的SQL统计,我们之前遇到过有的项目SQL语句几百KB,导致SQL统计占用过多内存。

有的项目使用OpenFeign + PathVariable/RequestParam 请求Restful接口,但指标生成时,取了动态生成的uri导致指标数量膨胀。

有的项目使用MyBatis的foreach,传递了过多的参数,导致MyBatis生成的临时大对象存在过久,不能被及时回收。

有的组件使用JVM Runtime#addShutdownHook注册钩子销毁资源,那这个对象会一直被JVM Hook引用,若创建了很多实例,则都不会被回收。

二、内存分配速率过大

可参考监控面板:GC内存分配,了解每秒钟新生代分配了多少内存、多少内存晋升到了老年代。

内存分配过大,可能是一次性加载的数据过多,通常伴随着慢请求、慢查询、不合理的报表导入导出或文件上传下载机制。

对G1来说,大对象(Humongous)会直接在老年代分配,其他GC算法也有类似机制,超过一定阈值直接晋升到老年代。

如果业务逻辑执行过久,则会导致对象不能被及时回收。

建议通过应用大盘的常用排行榜 ( HTTP慢接口/慢SQL排行 ) 或查看当时的Accesslog、应用日志来筛选可能的请求,尽可能优化代码实现,降低单次执行的资源消耗

另外,可尝试 hawk的HeartBeat 、arthas 查看当时的线程堆栈,找到可能的慢请求。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐