使用MAT 分析heap dump

  1. 下载及设置
    官方下载: https://www.eclipse.org/mat/
    JDK 最好用最新的 JDK, 因为最新的基本优化最多.
    根据你分析的 heap dump 的大小, 有时候需要调整 MAT 的 heap 的大小. 这个参数在 MAT 根目录的 MemoryAnalyzer.ini 文件里面. 我经常分析 30G 以下的 heap, 基本设置为 27G, 是 JVM 使用压缩指针来加速.
    sjc-sreop-001.png

    另外, 对于 HPROF 的 dump 来说, 我经常设置为非严格 parse, 因为有时候有点错误, 不影响分析:
    sjc-sreop-002.png

    文档: MAT 自带文档在 Help -> Help Contents 菜单里面, 或者在线版本的文档

  2. 分析
    正常情况下当你打开一个 heap dump 之后, 它会问你是否自动诊断内存泄漏, 如果你不是为了诊断内存泄漏, 可以取消这步.
    Histogram: 是按照类的实例数量聚集, 能很快发现包含大量实例的类. 一般情况下 char[] 或者 String 都在最上面, 这基本没有问题.
    Dominator Tree: 对于诊断内存泄漏非常有用, 如果能抓到一个对象 dominate 很多实例, 基本你找到了问题所在.
    OQL: 就像查询数据库的 SQL 语言, 非常方便的查找任何对象, 实例;
    Threads: 查看当前heap的所有线程, 对于发现某个对象是怎么被创建, 或引用的非常有帮助.

更多关于如何使用 OQL: Java heap dump OQL samples - where
------ 未完待续 -------

jmap & jhat

jmap 用来输出 JVM 的heap 相关信息, 或者生成 heap dump. 可以对一个正在运行的 JVM 进程使用这个命令, 也可以对一个 core dump. 它的功能基本都被 jcmd 命令所替代

jmap -histo <pid>  //jcmd <pid> help GC.class_histogram
jmap -histo:live <pid>   //jcmd <pid> help GC.class_histogram -all
jmap -clstats <pid>   //jcmd <pid> GC.class_stats
jmap -finalizerinfo <pid>  //jcmd <pid> GC.finalizer_info
jmap -dump:format=b,file=/tmp/heapdump.hprfo <pid>  //jcmd <pid> GC.heap_dump
jmap -dump:live,format=b,file=/tmp/heapdump.hprfo <pid>  //jcmd <pid> GC.heap_dump -all

jhat 对 heap dump 分析, 然后起一个本地 web 服务器, 开 7000 端口, 使用户可以在浏览器通过 OQL 查看 heap 里面的信息. 因为功能不够强大, 基本都用其他工具, 比如 MAT 或 Java VisualVM.

Microservices Patterns 读书笔记

  1. Preface
    松耦合, 小团队(Amazon 2 个披萨团队), 快速部署, 快速迭代, 清晰的接口定义(API), 推到重来

  2. 如何拆解为 micro service? 拆到哪种粒度? 如何权衡?

  3. 微服务带来的问题?
    a. 服务发现?
    b. 服务快速部署?
    c. 监控, 隔离, 修复?
    d. 服务拆分的艺术?

  4. 分布式服务带来的挑战?
    a. 数据一致性/最终一致性;
    b. 如何从组件 crash 中恢复?
    c. 如何整体可控?

  5. 异步消息带来的问题?

  6. 如何保证数据的一致性?

我这个 Java 应用的堆内存过几天会爆掉吗?

今天遇到一个有意思案例和一个有趣的问题.

某应用发布了新版本到集成测试环境(Staging), 他们发现有监控提示他们可能有内存泄漏, 于是这些开发人员做了很多 heap dump, 自己查了好几天, 都没查出所以然. 最后必须上线的期限快到了, 还没查出来, 索性先在生产上线一台去跑跑看, 监控了几个小时, 啥问题都没发生. 不过最后他们还是不放心, 转而寻求 SRE 帮助他们去查查.

拿到这个案例之后, SRE 线上看了生产上这台机器, 发现已经正常运行了8个小时, 内存, CPU, 线程等一切正常, 查看 GC 日志, 也没发现任何毛病. 于是问开发人员, 你们在集成测试环境到底看到了啥, 为啥怀疑内存泄漏. 于是他们果然给了一个显示 "可能有内存泄漏" 字样的截图. 于是 SRE 有去看了下这台 QA 环境的机器, 终于发现了问题.

原来他们对于不同的环境设置的内存 heap 大小是不一样的. 对于 production 环境, 他们设置的是4.8G 总 heap 大小, 对于其它环境, 一概设置为2.2G 的 heap 大小. 查看 production 环境的 heap 使用情况, 可以明显看到老年代 heap 大小在每次 CMS 并发 GC 之后, 大概占用1.4G, 还有很多空闲可用. 而对于总 heap 大小只有2.2G 的 QA 机器而言, 它的老年大总大小只给了1.4G, 所以等应用起来之后, 和生产环境一样, 一旦有业务请求进来, 最基本都要1.4G heap 被占用. 所以它就不停的做老年代 GC, 大多数情况下做 CMS GC 的速度跟不上新申请内存的速度, 一旦有个请求进来, 要使用的内存稍多一点, 年轻代立马被占满, 开始向老年代转移对象, 老年代就开始触发 Full GC. 于是监控就推测: 这个应用有内存泄漏.

所以这个应用根本么有内存泄漏, 只是因为他稳定运行(stable)之后, 就需要这么多堆内存. 只是因为在 QA 环境设置的太小, 导致监控系统误认为它有内存泄漏. 所以增大 QA 环境的堆内存参数, 是一个正解.

当 SRE 把这个解释告诉开发人员之后, 他们抛出了一个让我印象深刻的问题: "虽然现在生产上没有问题, 是不是因为它设置的堆内存比较大, 如果这样, 是不是运行3天之后, 就会导致像 QA 一样的堆内存不够的问题?".

如果真的 N 天之后, 堆内存被用光, 那确实是代码有问题, 如果代码没问题, 即便是 N 年之后, 堆内存也还是够的. 那么如何验证这个问题呢? 我们从堆内存的的 GC log view来看, 基本能判断这个问题. 下面是一个 Java 应用运行几天的 GC log view 图, 从图里可见, 每次老年代 CMS GC 之后, 老年代都回到同一个水平, 这就是一个没有内存泄漏的形状, 也是一个非常正常的锯齿形状. 如果有内存泄漏, 每次老年代 GC 之后, 它的使用大小都会增加. 久而久之, 老年代即使 GC 之后, 也没有了可用空间. 当然, 这里只是讨论 heap 的内存泄漏, 不考虑永久代, 原生内存等泄漏.
gcDayByDay.png

这里就涉及到一个 stablized heap size 问题. 所谓 stablized heap, 是指程序稳定运行一段时间,已经达到一个平稳的状态, 所有会被遇到的请求, 基本都执行过一定数量, 这时候如果对他做 Full GC, GC 之后留下的堆空间, 我们称之为 stablized heap size. 这个大小在很多时候, 都是设置堆大小的依据. 当一个请求进来之后, 它中间产生的对象实例, 不管是否移动到老年代, 在一定时间之后, 都会被回收. 这些不被回收, 一直在 heap 里面的实例对象, 是为了稳定运行这个程序所必须的对象.

所以, 要推测一个应用是不是过几天堆内存就会被爆掉, 只要查看一段时间的这个 heap GC log 基本就能判断.