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

Java heap space
这是最常见的一个 OOM 问题了,谁还没经历过一个 Heap OOM呢?
当堆内存被塞满之后,一边 GC 无法及时回收,一边又在继续创建新对象,Allocator 无法分配新的内存之后,就会送一个 OOM 的错误:
java.lang.OutOfMemoryError: Java heap space
分析解决起来无非是那几步:
dump 堆内存
通过 MAT、YourKit、JProfiler 、IDEA Profiler 等一系列工具分析dump文件
找到占用内存最多、最大的对象,看看是哪个小可爱干的
分析代码,尝试优化代码、减少对象创建
增加 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 打满的情况。
出现这个错误的时候,从监控角度看起来可能是这个样子:
请求量可能并不大
不停 GC,并切暂停时间很长
时不时的还有新的请求,但响应时间很高
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数据,比较主流的做法是:
通过 Arthas 之类的工具,查看 ClassLoader、loadClassess 的数据,分析数量较多的 ClassLoader 或者 Class
打印每个 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.
#

更多的解释,可以参考我这篇《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 权限的工具,可是得领导审批申请权限的……排查成本真的很高

排查 Native 内存的基本的思路是:
pmap 查看内存地址映射,定位可疑内存块、分析内存块数据
strace 手动追踪进程系统调用,分析内存分配的系统调用链路
更换jemalloc/tcmalloc之类的内存分配器(或者 async-profiler有个支持native 分析的分支)追踪malloc的调用链路
目前最常见的 Native 内存泄漏场景,是 JDK 的 Inflater/Deflater 这俩卧龙凤雏,功能是提供 GZIP 的压缩、解压,在默认 glibc 的 malloc 实现下,很容易出现“内存泄漏”。如果出现 Native 内存泄漏,可以先看看应用里有没有 GZIP 相关操作,说不定有惊喜。
好了,各类风格的 OOM 都感受完了,到底哪一个更能打动你呢?
作者:京东保险 蒋信
来源:京东云开发者社区 转载请注明来源
完蛋!我被 Out of Memory 包围了!的更多相关文章
- 【NX二次开发】NX内部函数,libugui.dll文件中的内部函数
本文分为两部分:"带参数的函数"和 "带修饰的函数". 浏览这篇博客前请先阅读: [NX二次开发]NX内部函数,查找内部函数的方法 带参数的函数: bool A ...
- Java 堆内存与栈内存异同(Java Heap Memory vs Stack Memory Difference)
--reference Java Heap Memory vs Stack Memory Difference 在数据结构中,堆和栈可以说是两种最基础的数据结构,而Java中的栈内存空间和堆内存空间有 ...
- 笔记:Memory Notification: Library Cache Object loaded into SGA
笔记:Memory Notification: Library Cache Object loaded into SGA在警告日志中发现一些这样的警告信息:Mon Nov 21 14:24:22 20 ...
- 浅析java内存模型--JMM(Java Memory Model)
在并发编程中,多个线程之间采取什么机制进行通信(信息交换),什么机制进行数据的同步? 在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的. 线程之间通过共享程序公共的状态,通 ...
- mysqld: Out of memory 解决办法(mysql)
自己配置的XWAMP环境,默认下没有详细配置mysql的my.ini,一方面不同服务器的配置不一样,另一方面按照默认为空的方式也一直没有出现过问题.不过最近服务器挂掉了,出现的症状是: 网站不能打开, ...
- 【译】Getting Physical With Memory
当我们试图去了解复杂系统时,去除其抽象层,直接关注最底层,我们会更容易去理解.使用这种方法,我们来看一下内存和 I/O 接口的最简单和基础的层:处理器和总线的接口.这些细节是更上层问题的基础,例如线程 ...
- [LeetCode] Surrounded Regions 包围区域
Given a 2D board containing 'X' and 'O', capture all regions surrounded by 'X'. A region is captured ...
- 使用ANTS Performance Profiler&ANTS Memory Profiler工具分析IIS进程内存和CPU占用过高问题
一.前言 最近一段时间,网站经常出现两个问题: 1.内存占用率一点点增高,直到将服务器内存占满. 2.访问某个页面时,页面响应过慢,CPU居高不下. 初步判断内存一点点增多可能是因为有未释放的资源一直 ...
- Android中基于CGroup的memory子系统HAL层分析-lmkd
Android在内存管理上于Linux有些小的区别,其中一个就是引入了lowmemorykiller.从lowmemorykiller.c位于drivers/staging/android也可知道,属 ...
- Automated Memory Analysis
catalogue . 静态分析.动态分析.内存镜像分析对比 . Memory Analysis Approach . volatility: An advanced memory forensics ...
随机推荐
- EF6连接oracle
最近项目用到oracle,一直使用sql server,ef很方便连接mssql,但是连接oracle的方法网上很多文章,尝试很多次终于搞定,dbfirst或者codefirst也是可以的. 安装OD ...
- 趣图|代码重构前vs重构后
前言 很多程序员对自己写的代码平时很随心所欲,但当有一天让他维护他人的代码,他就会抓狂,很容易激发他体内重构的瘾.(大多数程序员审阅完别人代码后,先会忍不住吐槽一番,然后会忍不住想重构一把,) 在我看 ...
- 安装deb包
输入命令: sudo dpkg -i file.deb
- OpenAI API访问速度不佳?试试用Vercel来加速!
前言 众所周知,使用openAI API在国内访问体验并不佳,经常遇到访问较慢或者访问失败的问题.本文着重讲讲怎么解决这个问题,让我们日常开发和使用能够更方便的体验到AI带来的便利 为了帮大家省钱,也 ...
- 面试再也不怕问ThreadLocal了
要解决多线程并发问题,常见的手段无非就几种.加锁,如使用synchronized,ReentrantLock,加锁可以限制资源只能被一个线程访问:CAS机制,如AtomicInterger,Atomi ...
- SpringBoot3之Web编程
标签:Rest.拦截器.swagger.测试; 一.简介 基于web包的依赖,SpringBoot可以快速启动一个web容器,简化项目的开发: 在web开发中又涉及如下几个功能点: 拦截器:可以让接口 ...
- Linux文件管理知识查找文件(第二篇)
Linux文件管理知识:查找文件(第二篇) 上篇文章详细介绍了linux系统中查找文件的工具或者命令程序locate和find命令的基本操作.那么,今天这篇文章紧接着查找文件相关操作内容介绍. Fin ...
- 升级java11后,maven命令打包报错
一.问题 升级java11后,maven命令打包报错: mvn clean package -Dmaven.test.skip=true [ERROR] Failed to execute goal ...
- 快速理解DDD领域驱动设计架构思想-基础篇
1 前言 本文与大家一起学习并介绍领域驱动设计(Domain Drive Design) 简称DDD,以及为什么我们需要领域驱动设计,它有哪些优缺点,尽量用一些通俗易懂文字来描述讲解领域驱动设计,本篇 ...
- 使用vscodep快速编写markdown
写在前面 这是一篇基于 vscode 配置,用于书写 markdown 的文章 为了方便快速书写 markdown 真想使用一些便捷的快捷键去生成一些自己常用的格式或者是模版,于是自己基于自己的个人习 ...