1. 工具准备
本章节除了用到上一章提到的 jcmd 之外, 还会使用到 VisualVM 工具此工具在 JDK 安装目录 \ bin 下, 文件名为 jvisualvm 你也可以在
http://visualvm.github.io/download.html
下载到最新的版本
VisualVM 使用各种技术 (包括 jvmstat,JMX,Serviceability Agent(SA) 和 Attach API)来进行故障定位至少需要具备 JDK 的版本 1.4+
我使用的是上述链接中下载的 jvisualvm1.4 版本
官方中文教程:
http://visualvm.github.io/documentation.html
建议: 将 etc 下 visualvm 配置文件中的 - Xmx 最大堆内存的大小调大一些, 否则在加载 dump 文件过程中很容易发生 oom
2. 故障模拟
今天我们要模拟的故障是一种常见的内存泄漏源代码如下:
- package com.brianxia.error;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Random;
- public class MemoryLeak {
- // 存储内存泄漏的数据
- public static Map < String,
- String > data = new HashMap < >();
- public static void addData(String key) {
- data.put(key, "");
- }
- public static void deleteData(String key) {
- data.remove(key, "");
- }
- public static void throwEx() throws Exception {
- throw new RuntimeException("leak");
- }
- // 内存泄漏代码, 添加数据之后抛出异常, 从而无法执行删除数据的代码, 造成内存泄漏的现象
- public static void leak() throws Exception {
- Random random = new Random();
- String key = String.valueOf(random.nextInt());
- addData(key);
- throwEx();
- deleteData(key);
- }
- public static void main(String[] args) {
- while (true) {
- try {
- leak();
- Thread.sleep(100);
- } catch(Exception e) {
- if (! (e instanceof RuntimeException)) e.printStackTrace();
- }
- }
- }
- }
这是一段最简单的内存泄漏代码, 本意是在 leak 函数中, 通过 add 和 delete, 回收掉添加到 hashmap 中的数据, 但是在执行 add 和 delete 的中间发生了异常, 所以代码路经直接会跑到 main 函数中的异常捕获中, 从而 hashmap 中的数据永远不会被回收了
3. 故障分析
关于如何识别是否有内存泄漏, 不在本文讨论范畴内, 后续会更新相应的文章首先我们用上一次提到的 jcmd 来生成 dump 文件
- g>jcmd 6172 GC.heap_dump d:\dump_leak
- 6172:
- Heap dump file created
打开 visualvm, 选择 load 刚才生成的文件
Image 1.png
选择 Objects, 查看下目前 JVM 中的对象状态
Image 2.png
Image 3.png
从上图中可以看到, 我们的代码产生了非常多的对象, 其中主要是 char[]Hashmap 的 NodeString 那么这个时候我们就需要根据我们的项目具体进行分析了, 首先我们的代码中存在 HashMap, key 的数据类型是 String, 而 String 的底层实现又是 char[], 这三个的数量可以看到基本一致
从上面的信息我们可以推断出, 我们代码实现中存在内存泄漏(当然也有可能是未进行 GC, 因为是演示用例, 我们可以在 visualvm 上手动执行 GC)
image.png
点开详细的 Object 列表, 可以看到相关的 reference 信息从上图可以知道, 我们的 Hashmap Node 主要是在 MemoryLeak 类中的 static 变量 data 中被引用到, 无法得到释放
之后就需要各位小伙伴去查验整个变量的生命周期, 确认为何资源没有被回收
4. 总结
以上就是基本的生产下处理问题的流程, 需要注意的是, 生产上可以打开
- XX: +HeapDumpOnOutOfMemoryError - XX: HeapDumpPath = $ {目录
}
这两个参数, 让发生 OOM 的时候自动生成 dump 用于后续分析当然最好的情况是在测试环境中能够通过分析 heap 信息发现问题, 而不是到生产上再去解决
来源: http://www.jianshu.com/p/065d12dd3e44