JDK 自带了一些工具,用于监控 JVM 的运行情况,例如 `jconsole`,`jvisualvm`,也有一些第三方的工具,例如 [mat](https://eclipse.dev/mat/),[yourkit java profiler](https://www.yourkit.com/java/profiler/features/),这里推荐 `mat`,非常的好用,`yourkit` 的则是界面更现代化一点,功能也更多,是收费的,多出来的功能其实也没什么用,因为本身也是依靠 JVM 提供的接口。
关于 JVM 的内存模型网上有很多分享了,感觉大多都是复制粘贴,这里也不想说这个,我觉得所谓的 'JVM调优',本身就是个伪命题,JVM 不需要调优,默认运行就好,更多的还是代码层面的问题,瞎折腾不如不折腾,有那工夫可以多审查下代码,或者升级 JVM ,能带来的提升都远比所谓的 '调优' 大。
---
今天主要想讨论的问题是,当 JVM 内存占用过高,或者出现 OOM 的时候,如何去分析。
首先当前就是转储内存了,可以设置参数 `-XX:+HeapDumpOnOutOfMemoryError` `-XX:HeapDumpPath=/xxx/heapdump.hprof` 在内存溢出时转储堆栈,同时利用上述软件进行分析,或者 JVM 进程还没有退出的时候,用 `jmap` 等工具进行转储堆栈。
这里我想到的排查思路就是,看内存占用的分布,哪些代码占用了高内存,然后再去检查对应的代码。
这里拿 `mat` 来说,这里我们主要使用 `dominator tree` 这个工具,至于为什么接下来会说。

这里我们可以清楚的看到 MyBatis 相关代码占用了非常大的内存,应该是在启动的时候解析 XML,并且缓存相应的结果,通过展开树结果,可以一层层往下进行分析。
这里说下界面上的 `shalldow heap` 和 `retained heap`,也是我主要想分享这篇文章的原因。
`shalldow heap` 是指只计算对象本身直接占用的内存,例如一个 int ,一个boolean,对于引用的对象,我们都知道对象其实就是一个指针,那就只占用一个指针的大小,通常是一个int;但是在 Java 中,String 也是一个对象,也就是说,无论一个对象包含了一个多么大的 String,反应到对象的统计里,也就是一个指针的大小,这个不是我想要的结果。
反应到工具里,像是 `jvisualvm` 这种工具只能查看 `shalldow heap` 这种内存,会根据对象类型,展示对象数量和占用内存,像是下面这样。

结果就是清一色的 byte[],因为 String 本身也是由 byte[] 组成,所以这种我觉得就没什么意义。
`retained heap` 这种才正是我一直再找得,当初就是像找一个工具,能递归式的获得一个对象占用的所有内存,包含子对象的占用,这样才能比较方便体现哪里占用的内存最大,所以就看到了 `mat`。
`dominator tree` 这种就是按照 GC Root 的顺序,自上而下遍历所有的对象,计算他们占用的大小,然后以树的形式展示在我们面前。
---
不过,所谓 Heap Dump,当前就只有 Heap 的情况,Java 除了 Heap 区,也有 Non-heap 区,通常对于类的元数据等相关存储,就在相关 Non-heap 区,还有一些直接内存引用,这部分内存是不好进行分析的,虽然加上参数 `-XX:NativeMemoryTracking=summary` 也可以打印一些信息。
讲讲java的内存分析