首页 > 其他分享 >完蛋!我被 Out of Memory 包围了! | 京东云技术团队

完蛋!我被 Out of Memory 包围了! | 京东云技术团队

时间:2023-11-07 17:03:53浏览次数:31  
标签:Java OOM Direct GC 内存 JVM Memory 京东 Out

  • 是极致魅惑、洒脱自由的Java heap space
  • 是知性柔情、温婉大气的GC overhead limit exceeded
  • 是纯真无邪、活泼可爱的Metaspace
  • 如果以上不是你的菜,那还有……
  • 刁蛮任性,无迹可寻的CodeCache
  • 性感火辣、心思细腻的Direct Memory
  • 高贵冷艳,独爱你一人的OOM Killer
  • 总有一款,能让你钟情!BUG 选择权,现在交由你手!

完蛋!我被 Out of Memory 包围了! | 京东云技术团队_Memory

Java heap space

这是最常见的一个 OOM 问题了,谁还没经历过一个 Heap OOM呢?

当堆内存被塞满之后,一边 GC 无法及时回收,一边又在继续创建新对象,Allocator 无法分配新的内存之后,就会送一个 OOM 的错误:

java.lang.OutOfMemoryError: Java heap space

分析解决起来无非是那几步:

  1. dump 堆内存
  2. 通过 MAT、YourKit、JProfiler 、IDEA Profiler 等一系列工具分析dump文件
  3. 找到占用内存最多、最大的对象,看看是哪个小可爱干的
  4. 分析代码,尝试优化代码、减少对象创建
  5. 增加 JVM 堆内存、限制请求数、线程数、增加节点数量等

常见类库使用误区

尤其是一些工具库,尽可能的避免每次新建对象,从而节省内存提升性能。

大多数主流的类库,入口类都保证了单例线程安全,全局维护一份即可

举一些常见的错误使用例子:

Apache HttpClient

CloseableHttpClient ,这玩意相当于一个“浏览器进程”了,背后有连接池连接复用,一堆机制的辅助类,如果每次都 new 一个,不仅速度慢,而且浪费了大量资源。

比较正常的做法是,全局维护一个(或者根据业务场景分组,每组一个)实例,服务启动时创建,服务关闭时销毁:

CloseableHttpClient httpClient = HttpClients.custom()
                .setMaxConnPerRoute(maxConnPerRoute)
                .setMaxConnTotal(maxConnTotal)
                /// ...
                                 .build();
Gson

毕竟是 Google 的项目,入口类自然也是实现了线程安全,全局维护一份 Gson 实例即可

Jackson

Jackson 作为 Spring MVC 默认的 JSON 处理库,功能强大、用户众多,xml/json/yaml/properties/csv 各种主流格式都支持,单例线程安全自然也是 ok 的,全局维护一份 ObjectMapper 即可。

GC overhead limit exceeded

这个错误比较有意思,上面的 Java heap space 是内存彻底满了之后,还在持续的创建新对象,此时服务会彻底假死,无法处理新的请求。

而这个错误,只是表示 GC 开销过大,Collector 花了大量的时间回收内存,但释放的堆内存却很小,并不代表服务死了

此时程序处于一种很微妙的状态:堆内存满了(或者达到回收阈值),不停的触发 GC 回收,但大多数对象都是可达的无法回收,同时 Mutator 还在低频率的创建新对象。

出现这个错误,一般都是流量较低的场景,有太多常驻的可达对象无法回收,但是吧,GC 后空闲的内存还可以满足服务的基本使用

不过此时,已经在频繁的老年代GC了,老年代又大对象又多、在现有的回收算法下,GC 效率非常低并切资源占用巨大,甚至会出现把 CPU 打满的情况。

出现这个错误的时候,从监控角度看起来可能是这个样子:

  1. 请求量可能并不大
  2. 不停 GC,并切暂停时间很长
  3. 时不时的还有新的请求,但响应时间很高
  4. CPU 利用率很高

毕竟还是堆内存的问题,排查思路和上面的Java heap space没什么区别。

Metaspace/PermGen

Metaspace 区域里,最主要的就是 Class 的元数据了,ClassLoader 加在的数据,都会存储在这里。

MetaSpace 初始值很小,默认是没有上限的。当利用率超过40%(默认值 MinMetaspaceFreeRatio)会进行扩容,每次扩容一点点,扩容也不会直接 FullGC。

比较推荐的做法,是不给初始值,但限制最大值:

-XX:MaxMetaspaceSize=

不过还是得小心,这玩意满了后果很严重,轻则 Full GC,重则 OOM:

java.lang.OutOfMemoryError: Metaspace

排查 MetaSpace 的问题,主要思路还是追踪 Class Load数据,比较主流的做法是:

  1. 通过 Arthas 之类的工具,查看 ClassLoader、loadClassess 的数据,分析数量较多的 ClassLoader 或者 Class
  2. 打印每个 class 的加载日志:-XX:+TraceClassLoading -XX:+TraceClassUnloading

下面介绍几个常见的,可能导致 MetaSpace 增长的场景:

反射使用不当

JAVA 里的反射,性能是非常低的,以反射的对象必须得缓存起来。尤其是这个Method对象,如果在并发的场景下,每次都获取新的 Method,然后 invoke 的话,用不了多久 MetaSpace 就给你打爆!

简单的说,并发场景下,Method.invoke 会重复的动态创建 class,从而导致 MetaSpace 区域增长,具体分析可以参考笨神的文章《从一起GC血案谈到反射原理》。

用反射时,尽可能的用成熟的工具类,Spring的、Apache的都可以。它们都内置了reflection相关对象的缓存,功能又全性能又好,足以解决日常的使用需求。

一些 Agent 的 bug

一些 Java Agent,静态的和运行时注入的都算。基于 Instrumentation 这套 API 做了各种增强,一会 load 一会 redefine 一会remove的,如果不小心出现 BUG,也很容易生成大量动态的 class,从而导致 metaspace 打满。

动态代理问题

像 Spring 的 AOP ,也是基于动态代理实现的,不管是 CgLib 还是 JDK Proxy,不管是 ASM 还是 ByteBuddy。最终的结果都逃不开动态创建、加载 Class,有这两个操作,那 Metaspace 必定受影响。

Spring 的 Bean 默认是singleton的,如果配置为prototype,那么每次 getBean 就会创建新的代理对象,重新生成动态的 class、重新 define,MetaSpace 自然越来越大。

Code Cache

Code Cache 区域,存储的是 JIT 编译后的热点代码缓存(注意,编译过程中使用的内存不属于 Code cache),也属于 non heap 。

如果 Code cache 满了,你可能会看到这么一条日志:

Server VM warning: CodeCache is full. Compiler has been disabled.

此时 JVM 会禁用 JIT 编译,你的服务也会开始变慢。

Code Cache 的上限默认比较低,一般是240MB/128MB,不同平台可能有所区别。

可以通过参数来调整 Code Cache 的上限:

-XX:ReservedCodeCacheSize=

只要尽量避免过大的Class、Method ,一般也不太会出现这个区域被打满的问题,默认的 240MB/128MB 也足够了

Direct Memory

Direct Memory 区域,一般称之为直接内存,很多涉及到 磁盘I/O ,Socket I/O 的场景,为了“Zero Copy”提升性能都会使用 Direct Memory。

就比如 Netty ,它真的是把 Direct Memory 玩出了花(有空写一篇 Netty 内存管理分析)……

使用 Direct Memory时,相当于直接绕过 JVM 内存管理,调用 malloc() 函数,体验手动管理内存的乐趣~

不过吧,这玩意使用比较危险,一般都配合 Unsafe 操作,一个不小心地址读写的地址错误,就能得到一个 JVM 给你的惊喜:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffdbd5d19b4, pid=1208, tid=0x0000000000002ee0
#
# JRE version: Java(TM) SE Runtime Environment (8.0_301-b09) (build 1.8.0_301-b09)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.301-b09 mixed mode windows-amd64 compressed oops)  
# Problematic frame:
# C  [msvcr100.dll+0x119b4]
# 
# No core dump will be written. Minidumps are not enabled by default on client versions of Windows
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

完蛋!我被 Out of Memory 包围了! | 京东云技术团队_JVM_02

更多的解释,可以参考我这篇《Java中的Heap Buffer与Direct Buffer

这个 Direct Memory 区域,默认是无上限的,但为了防止被 OS Kill,还是会限制一下,给个256MB或者更小的值,防止内存无限增长:

-XX:MaxDirectMemorySize=

如果 Direct Memory 达到 MaxDirectMemorySize 并且无法释放时,就会得到一个 OOM错误:

java.lang.OutOfMemoryError: Direct buffer memory

Linux OOM Killer

跳出 JVM 内存管理之后,当 OS 内存耗尽时,Linux 会选择内存占用最多,优先级最低或者最不重要的进程杀死。

一般在容器里,主要的进程就是肯定是我们的 JVM ,一旦内存满,第一个杀的就是它,而且还是 kill -TERM (-9)信号,打你一个猝不及防。

如果 JVM 内存参数配置合理,远低于容器内存限制,还是出现了 OOM Killer 的话,那么恭喜你,大概率是有什么 Native 内存泄漏。

这部分内存,JVM 它还管不了。

除了 JVM 内部的 Native 泄漏 BUG 这种小概率事件外,大概率是你引用的第三方库导致的。

这类问题排查起来非常麻烦,毕竟在 JVM 之外,只能靠一些原生的工具去分析。

而且吧,这种动不动就要 root 权限的工具,可是得领导审批申请权限的……排查成本真的很高

完蛋!我被 Out of Memory 包围了! | 京东云技术团队_JVM_03

排查 Native 内存的基本的思路是:

  1. pmap 查看内存地址映射,定位可疑内存块、分析内存块数据
  2. strace 手动追踪进程系统调用,分析内存分配的系统调用链路
  3. 更换jemalloc/tcmalloc之类的内存分配器(或者 async-profiler有个支持native 分析的分支)追踪malloc的调用链路

目前最常见的 Native 内存泄漏场景,是 JDK 的 Inflater/Deflater 这俩卧龙凤雏,功能是提供 GZIP 的压缩、解压,在默认 glibc 的 malloc 实现下,很容易出现“内存泄漏”。如果出现 Native 内存泄漏,可以先看看应用里有没有 GZIP 相关操作,说不定有惊喜。


好了,各类风格的 OOM 都感受完了,到底哪一个更能打动你呢?

作者:京东保险 蒋信

来源:京东云开发者社区 转载请注明来源

标签:Java,OOM,Direct,GC,内存,JVM,Memory,京东,Out
From: https://blog.51cto.com/u_15714439/8237003

相关文章

  • 一个java文件的JVM之旅 | 京东物流技术团队
    准备我是小C同学编写得一个java文件,如何实现我的功能呢?需要去JVM(JavaVirtualMachine)这个地方旅行。变身我高高兴兴的来到JVM,想要开始JVM之旅,它确说:“现在的我还不能进去,需要做一次转换,生成class文件才行”。为什么这样呢?JVM不能直接加载java文件的原因:Java源代码中包含了许多......
  • 体验问题哪里找?点击链接获取答案~ | 京东云技术团队
    用户体验(UserExperience,简称UX)是指用户在与产品、系统或服务进行交互的过程中所产生的主观感受和情感反应。它涵盖了用户对产品或服务的使用过程中所体验到的方方面面,包括界面设计、交互流程、功能性、易用性、可靠性、响应速度、视觉美感等。我们为什么需要注重“用户体验”呢?引......
  • DDD技术方案落地实践 | 京东云技术团队
    1.引言从接触领域驱动设计的初学阶段,到实现一个旧系统改造到DDD模型,再到按DDD规范落地的3个的项目。对于领域驱动模型设计研发,从开始的各种疑惑到吸收各种先进的理念,目前在技术实施这一块已经基本比较成熟。在既往经验中总结了一些在开发中遇到的技术问题和解决方案进行分享。因为......
  • 通过 SAP UI5 IconTabBar 控件结合 FlexibleColumnLayout 实现多页面 Master-Detail
    本文也是来源于网络上一位朋友的咨询,这是这位朋友实际项目中的真实需求。本文介绍了一个实际项目中开发需求的详细实现过程。通过使用SAPUI5IconTabBar控件,我们可以让逻辑上属于不同业务范畴的界面,通过点击对应的Icon,以切换的方式,在同一块屏幕区域显示出来。IconTabBar结......
  • vue-router中重写push方法
    文章目的:看到一段代码不理解什么意思,查了一下是为了解决,重复跳转相同的路由,控制台报错。重写了方法后,能catch异常,不会在控制台报错了。代码:Router.prototype.push=functionpush(location){console.log(location,'location')returnrouterPush.call(this,locat......
  • A Tour Through TREE_RCU's Grace-Period Memory Ordering (翻译)
    原文:https://docs.kernel.org/RCU/Design/Memory-Ordering/Tree-RCU-Memory-Ordering.htmlAugust8,2017ThisarticlewascontributedbyPaulE.McKenneyIntroductionThisdocumentgivesaroughvisualoverviewofhowTreeRCU'sgrace-periodmemoryorderi......
  • How to resize slide dimensions without resizing any objects on the slide?
    IFyouarecompetenttounzipthepptxfileandmodifytheXMLitcanbedone,theslidesizewillchangebutthepictureswillnotchange(theywillmovethoughandyouwillhavetoadjustthepositions)Unzip>lookforPPT>presentationXMLandc......
  • YouTrack 在创建问题的时候如何切换项目
    最近在准备从JIRA中转换到YouTrack上。在创建问题的时候,JIRA是通过选择项目,然后单击创建,这个创建就会直接在项目中进行创建了。但是YouTrack不是这样的,感觉就是YouTrack的创建问题就是一个入口。其实我并不知道YouTrack的默认选择规则是什么。如果创建的问题不是你需要......
  • Slint 中的元素定位 (Positioning) 和布局 (Layout)
    基本逻辑Slint当中进行元素定位的基本逻辑是这样的:所有的可见元素都需要放置在窗口(window)中,每个元素都有x和y属性,这两个属性表示当前元素相对父元素的位置偏移。Slint计算某个元素在整个窗口中的位置时,会按照层级关系,一级一级将这个x和y的值进行累加,最终得到元素......
  • Veeam Backup & Replication v12-(5)Scale-Out Backup Repository
    VeeamBackup&Replication可使用多个不同类型的存储库:WindowsBackupRepositories,Windows备份存储库:NTFS或推荐的ReFS。LinuxBackupRepositories,inux备份存储库:XFS与Reflink。ObjectStorage:对象存储:指向对象存储。这在v12版本中提供,可用于SOBR的所有层,包括性能层。SharedFo......