前京东陌陌高级架构师的直播笔记分享

原文:前京东陌陌高级架构师的直播笔记分享(Java 内存问题排查和解决:内存概览,内存问题出现的原因,问题代码,案例分析)

上一周我有幸观看了高级架构师李国讲师的直播,内容是关于 Java 内存问题排查和解决。

 

下面是我做的笔记,在这里分享一下。


直播背景

直播讲师

李国,曾任京东、陌陌高级架构师。负责过京东金融调用链系统 SGM,以及数据库中间件 CDS 的开发工作。曾负责陌陌基础社交业务线的整体架构工作,对高并发下的 JVM 调优有丰富的经验。

主题

  • 了解 JVM 和操作系统的内存管理基本概念

  • 了解内存溢出和内存泄漏的原因和症状

  • 根据实例诊断/发现/解决内存问题


想了解更多,欢迎关注我的微信公众号:Renda_Zhang

内存

Linux 系统内存概览

  • 编译后地址是逻辑内存,需要经过翻译映射到物理内存

  • MMU 负责地址的转换

  • 可用内存 = 物理内存 + 虚拟内存 (swap)

  • RES实际内存占用

  • 可用内存 = free + buffers + cached

  • /proc/meminfo

    # cat /proc/meminfo
    MemTotal:        
    3881692 kB
    MemFree:          
    249248 kB
    MemAvailable:    
    1510048 kB
    Buffers:           92384
    kB
    Cached:          
    1340716 kB
    40+ more ...

JVM 基本内存划分

前京东陌陌高级架构师的直播笔记分享

内存区域

前京东陌陌高级架构师的直播笔记分享

  • 堆:JVM 堆中的数据,是共享的,是占用内存最大的一块区域

  • 虚拟机栈:Java 虚拟机栈,是基于线程的,用来服务字节码指令的运行

  • 程序计数器:当前线程所执行的字节码的行号指示器

  • 元空间:方法区就在这里,不是堆

  • 本地内存:其他的内存占用空间

Java 内存管理基本概念

  • Java 内存

    • Metaspace 默认无上限

    • 原方法区在这里

    • JVM 分配的 Java 内存对象

    • 通常使用 -Xmx -Xms 控制大小

    • Java 堆内存

    • 元空间(堆外)

  • 操作系统剩余内存

内存划分

前京东陌陌高级架构师的直播笔记分享

  • JVM 进程内存 = 堆内内存 + 堆外内存

  • 堆外内存 = 元空间 + CodeCache + 本地内存

  • 堆外内存和操作系统剩余内存是此消彼长的关系

  • 可分配内存大小 = 物理内存 + SWAP

  • 32 位内存限制 4GB,目前 ZGC 支持 16 TB内存

配置参数设置

  • 堆:-Xmx -Xms

  • 元空间:-XX:MaxMetaspaceSize -XX:MetaspaceSize

  • 栈:-Xss

  • 直接内存:-XX:MaxDirectMemorySize

  • 其它内存:无法控制

查看内存指令对比

  • jmap

    • 可以查看 堆内存 对象分布

    • 可以导出堆内存快照线下分析

  • pmap

    • 查看 进程内存 映像信息


内存问题出现的分析

垃圾回收

  • 自动垃圾回收:JVM 自动检测和释放不再使用的内存

  • Java 运行时 JVM 会有线程执行 GC,不需要程序员显示释放对象

  • GC 发生的实际由复杂的策略判断,自动触发,不受外部控制

  • 不同的垃圾回收算法、甚至不同的 JVM 版本,回收策略都不一样

  • 统计显示:OOM/ML 问题占比 5% 左右

  • 平均处理时间 40 天左右

内存问题两种形式

  • 内存溢出  OutOfMemoryError,简称OOM

    • 堆是最常见的情况

    • 堆外内存排查困难

  • 内存泄漏  Memory Leak,简称ML

    • 分配的内存没有得到释放

    • 内存一直在增长,有 OOM 风险

    • GC时该回收的回收不掉

    • 能够回收掉但很快又占满,产生压力

内存问题的影响

  • 发生 OOM Error,应用停止(最严重)

  • 频繁 GC,GC 时间长,GC 线程时间片占用高

  • 服务卡顿,请求响应时间变长

  • 排查困难

    • 问题时间跨度大

    • 问题解决耗费精力

    • 现场保护意识不足

简单问题场景

  • 物理内存不足

    • 主机物理内存非常小

    • 主机上应用进程非常多

  • 给应用 JVM 分配的内存小

  • 错误的引用方式,发生了内存泄漏。没有及时的切断与 GC roots 的关系

  • 并发量大,计算需要内存大

  • 没有控制取数范围(如分页)

  • 加载了非常多的Jar包

  • 对堆外内存无限制的使用

垃圾回收器介绍

  • CMS 将在 Java 14 正式移除

  • G1 主流应用的垃圾回收器

  • ZGC 大容量(16TB),低延迟(10ms)的垃圾回收器

  • MaxGCPauseMillis 预定目标,自动调整

  • G1HeapRegionSize 小堆区大小

  • InitiatingHeapOccupancyPercent  堆内存比例阈值,启动并发标记

可达性分析法

  • Reference Chain

  • GC 过程:找到活跃的对象,然后清理其他的

  • 引用级别

    • 强引用:属于最普通最强硬的一种存在,只有在和 GC Roots 断绝关系时,才会被消灭掉

    • 软引用:只有在内存不足时,系统则会回收软引用对象

    • 弱引用:当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象

    • 虚引用:虚引用主要用来跟踪对象被垃圾回收的活动

对象何时提升(Promotion)

  • 常规提升:对象够老

  • 分配担保  Survivor 空间不够,老年代担保

  • 大对象直接在老年代分配

  • 动态对象年龄判定

  • -XX:MaxTenuringThreshold 在 CMS 下默认为 6,G1 下默认为 15


排查内存问题

对比交通事故和内存故障

  • 事故发生方 = 具体的服务

  • 事故处理方 = 相关程序员  

  • 事故现场(拍照取证)= 问题发生快照

  • 后续处理 = 措施改进  

瞬时态和历史态

  • 瞬时态 - 现场保存

    • 是指当时发生的,快照类型的元素。

    • 体积大

  • 历史态 - 日志信息,监控

    • 指按照频率抓取的

    • 有固定监控项的资源变动图

    • 业务日志

    • GC日志  (http://gceasy.io/)

排查工具示例

  • ss -antp > $DUMP_DIR/ss.dump 2>&1

  • netstat -s > $DUMP_DIR/netstat-s.dump 2>&1

  • top -Hp $PID -b -n 1 -c > $DUMP_DIR/top-$PID.dump 2>&1

  • sar -n DEV 1 2 > $DUMP_DIR/sar-traffic.dump 2>&1

  • lsof -p $PID > $DUMP_DIR/lsof-$PID.dump

  • iostat -x > $DUMP_DIR/iostat.dump 2>&1

  • free -h > $DUMP_DIR/free.dump 2>&1

  • jstat -gcutil $PID > $DUMP_DIR/jstat-gcutil.dump 2>&1

  • jstack $PID > $DUMP_DIR/jstack.dump 2>&1

  • jmap -histo $PID > $DUMP_DIR/jmap-histo.dump 2>&1

  • jmap -dump:format=b,file=$DUMP_DIR/heap.bin $PID > /dev/null 2>&1


不同区域溢出示例

堆溢出

java -Xmx20m -Xmn4m -XX:+HeapDumpOnOutOfMemoryError - OOMTest
[18.386s][info][gc] GC(10) Concurrent Mark 5.435ms
[18.395s][info][gc] GC(12) Pause Full (Allocation Failure) 18M->18M(19M)
10.572ms
[18.400s][info][gc] GC(13) Pause Full (Allocation Failure) 18M->18M(19M)
5.348ms
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at OldOOM.main(OldOOM.java:20)

元空间溢出

java -Xmx20m -Xmn4m -XX:+HeapDumpOnOutOfMemoryError -XX:MetaspaceSize=16M -XX:MaxMetaspaceSize=16M MetaspaceOOMTest
6.556s][info][gc] GC(30) Concurrent Cycle 46.668ms
java.lang.OutOfMemoryError: Metaspace
Dumping heap to /tmp/logs/java_pid36723.hprof ..

直接内存溢出

java -XX:MaxDirectMemorySize=10M -Xmx10M OffHeapOOMTest
Exception in thread "Thread-2" java.lang.OutOfMemoryError: Direct buffer memory
    at java.nio.Bits.reserveMemory(Bits.java:694)
    at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
    at OffHeapOOMTest.oom(OffHeapOOMTest.java:27)...

栈溢出

java -Xss128K StackOverflowTest
Exception in thread "main" java.lang.StackOverflowError
    at
java.io.PrintStream.write(PrintStream.java:526)
    at
java.io.PrintStream.print(PrintStream.java:597)
    at
java.io.PrintStream.println(PrintStream.java:736)
    at
StackOverflowTest.a(StackOverflowTest.java:5)

问题代码

泄漏代码示例

前京东陌陌高级架构师的直播笔记分享

  • 由于没有重写 Key 类的 hashCodeequals 方法,造成了放入 HashMap 的所有对象,都无法被取出来

  • 它们和外界失联了

  • 如何修正:重写 Key 对象的 equalshashCode 方法

结果集失控示例

  • 错误代码:

    前京东陌陌高级架构师的直播笔记分享

  • 正确代码:

    前京东陌陌高级架构师的直播笔记分享

条件失控示例

前京东陌陌高级架构师的直播笔记分享

  • fullnameother 为空的时候

  • 正确方式:使用 limit 语句,分页的思路

万能参数示例

  • 错误代码:

    前京东陌陌高级架构师的直播笔记分享

  • 减少使用map作为参数的频率

  • 解决方式:拆分成专用的函数

  • 正确代码:

    前京东陌陌高级架构师的直播笔记分享

一些预防措施

  • 减少创建大对象的频率:比如 byte 数组的传递

  • 不要缓存太多的堆内数据:使用 guava 的 weak 引用模式

  • 查询的范围一定要可控:如分库分表中间件;ES 等有同样问题

  • 用完的资源一定要 close 掉:可以使用新的 try-with-resources 语法

  • 少用 intern:字符串太长,且无法复用,就会造成内存泄漏

  • 合理的 Session 超时时间

  • 少用第三方本地代码,使用Java方案替代

  • 合理的池大小

  • XML(SAX/DOM)、JSON 解析要注意对象大小


案例一

现象

  • 环境:CentOS7,JDK1.8,SpringBoot

  • G1 垃圾回收器

  • 刚启动没什么问题,慢慢放量后,发生了 OOM

  • 系统自动生成了 heapdump 文件

  • 临时解决方式:重启,但问题依然发现

信息收集

  • 日志:GC 的日志信息:内存突增突降,变动迅速

  1. 堆栈:Thread Dump 文件:大部分阻塞在某个方法上

  2. 压测:使用 wrk 进行压测,发现 20 个用户并发,内存溢出

wrk -t20 -c20 -d300s http://127.0.0.1:8084/api/test

-t 使用的线程数
-c 开启的连接数量
-d 持续压测的时间

MAT 分析

  • MAT 工具是基于 eclipse 平台开发的,本身是一个 Java 程序

  • 分析 Heap Dump 文件:发现内存创建了大量的报表对象

  • 堆栈文件获取:

jmap -dump:format=b,file=heap.bin 37340
jhsdb jmap  --binaryheap --pid  37340

解决

  • 分析结果:

    • 系统存在大数据量查询服务,并在内存做合并

    • 当并发量达到一定程度,会有大量数据堆积到内存进行运算

  • 解决方式:

    • 重构查询服务,减少查询的字段

    • 使用 SQL 查询代替内存拼接,避免对结果集的操作

    • 举例:查找两个列表的交集

案例二

现象

  • 环境:CentOS7,JDK1.8,JBoss

  • CMS 垃圾回收器

  • 操作系统 CPU 资源耗尽

  • 访问任何接口,响应都非常的慢

分析

  • 找到使用 CPU 最高的线程

  • 根据堆栈定位到是 GC 进程占用高 CPU

  • 发现是 GC 线程占用大量资源

  • 陷入僵局

    "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007ff9f8020000 nid=0x4f5e runnable
    "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007ff9f8021800 nid=0x4f5f runnable
    "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007ff9f8023800 nid=0x4f60 runnable
    "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007ff9f8025000 nid=0x4f61 runnable

进一步分析

  • 发现每次 GC 的效果都特别好,但是非常频繁

  • 了解到使用了堆内缓存,而且设置的容量比较大

  • 缓存填充的速度特别快

  • 结论:开了非常大的缓存,GC 之后迅速占满,造成 GC 频繁

  • 类似问题:

    • Websocket 心跳检测失效,造成链接不释放,无效包持续发送

    • 数据库连接持续创建,依靠 GC 进行回收

案例三

现象

  • java进程异常退出

  • java进程直接消失

  • 没有留下dump文件

  • GC日志正常

  • 监控发现死亡时,堆内内存占用很少,堆内仍有大量剩余空间

分析

  • XX:+HeapDumpOnOutOfMemoryError 不起作用

  • 监控发现操作系统内存持续增加

  • 可能:

    1. 被操作系统杀死 dmesg oom-killer

    2. System.exit()

    3. java com.cn.AA &

    4. kill -9

解决

  • 发现:在 dmesg 命令中发现确实被 oom-kill

  • 解决:给JVM少分配一些内存,腾出空间给其他进程

    kill -9 && kill -15

案例四

现象

  • Java 服务被 oom-kill

  • 操作系统内存 free 区一直减少,并无其他进程抢占资源

  • 堆内内存使用情况正常

  • 使用 top 命令,发现 RES 占用严重超出了 -Xmx 的设定

分析

  • 大概率发生了堆外内存溢出

  • 程序使用 unsafe 类操作了堆外内存

  • pmap 查看内存分布

  • gdb 导出内存块

  • perf 监控函数调用

  • gperftools 分析内存分配函数

解决

  • 发现:程序使用了 JNA 库,调用了 native 加密函数库,加密函数库存在内存管理 bug

  • 修复:修正 native 函数库的 bug

堆内和堆外内存问题区别

  • 堆内存问题

    • Java 进程内存持续增长

    • GC 显示 heap 区内存不足,GC 频繁

  • 本地内存问题

    • GC 日志显示,heap 区有足够的空间

    • Java 进程内存一直在增长


总结

步骤

一、问题发现(最困难)

  1. 确保加入了日志和自动转储参数

  2. 确定物理内存足够:free

  3. 确定 Java 进程内存足够:jmap

  4. 确定主机环境,剩余内存大小

  5. 查看 GClog 和其他日志

  6. 使用 jstack 对线程进行摸底

  7. 对堆外内存进行排查

  8. 保留现场

二、采取措施

三 、重复观察

四、问题解决

SWAP的启用和观测

前京东陌陌高级架构师的直播笔记分享


相关文章: