乐趣区

关于java:troubleshoot之使用JFR解决内存泄露

简介

尽管 java 有自动化的 GC,然而还会有内存泄露的状况。当然 java 中的内存泄露跟 C ++ 中的泄露不同。

在 C ++ 中所有被调配的内存对象都须要要程序员手动开释。然而在 java 中并不需要这个过程,一切都是由 GC 来主动实现的。那么是不是 java 中就没有内存泄露了呢?

要答复这个问题咱们首先须要界定一下什么是内存泄露。如果说有时候咱们不再应用的对象却不能被 GC 开释的话,那么就能够说产生了内存泄露。

内存泄露的次要起因就是 java 中的对象生命周期有长有短。如果长生命周期的对象援用了短生命周期的对象,就有可能造成事实上的内存泄露。

一个内存泄露的例子

咱们举一个内存泄露的例子,先定义一个大对象:

public class KeyObject {List<String> list = new ArrayList<>(200);
}

而后应用它:

public class TestMemoryLeak {public static HashSet<Object> hashSet= new HashSet();

    public static void main(String[] args) throws InterruptedException {
        boolean flag= true;
        while(flag){KeyObject keyObject= new KeyObject();
            hashSet.add(keyObject);
            keyObject=null;
            Thread.sleep(1);
        }
        System.out.println(hashSet.remove(new KeyObject()));
    }
}

在这个例子中,咱们将 new 进去的 KeyObject 对象放进 HashSet 中。
而后将 keyObject 置为空。

然而因为类变量 hashSet 还保留着对 keyObject 的援用,所以 keyObject 对象并不会被回收。

留神,最初一行咱们加了一个 hashSet.remove 的代码,来应用类变量 hashSet。
为什么要这样做呢?这样做是为了避免 JIT 对代码进行优化,从而影响咱们对内存泄露的剖析。

应用 JFR 和 JMC 来剖析内存泄露

Flight Recorder(JFR) 次要用来记录 JVM 的事件,咱们能够从这些事件中剖析出内存泄露。

能够通过上面的指令来开启 JFR:

java -XX:StartFlightRecording

当然咱们也能够应用 java 神器 jcmd 来开启 JFR:

jcmd pid JFR.dump filename=recording.jfr path-to-gc-roots=true

这里咱们应用 JMC 来图形化剖析一下下面的例子。

开启 JMC,找到咱们的测试程序,关上航行记录器。

能够看到咱们的对象在航行记录器期间调配了 4MB 的内存,而后看到整体的内存使用量是稳步回升的。

咱们什么时候晓得会有内存泄露呢?最简略的必定就是 OutOfMemoryErrors,然而有些很荫蔽的内存泄露会导致内存应用缓步上涨,这时候就须要咱们进行粗疏的剖析。

通过剖析,咱们看到内存应用在稳步上涨,这其实是很可疑的。

接下来咱们通过 JVM 的 OldObjectSample 事件来剖析一下。

OldObjectSample

OldObjectSample 就是对生命周期比拟长的对象进行取样,咱们能够通过钻研这些对象,来查看潜在的内存泄露。

这里咱们关注一下事件浏览器中的 Old Object Sample 事件,咱们能够在左下方看到事件的详情。

或者你能够应用 jfr 命令间接将感兴趣的事件解析输入:

jfr print --events OldObjectSample flight_recording_1401comflydeanTestMemoryLeak89268.jfr   > /tmp/jfrevent.log

咱们看一个具体的输入 Sample:

jdk.OldObjectSample {
  startTime = 19:53:25.607
  allocationTime = 19:50:51.924
  objectAge = 2 m 34 s
  lastKnownHeapUsage = 3.5 MB
  object =  [java.lang.Object[200]
  ]
  arrayElements = 200
  root = N/A
  eventThread = "main" (javaThreadId = 1)
  stackTrace = [java.util.ArrayList.<init>(int) line: 156
    com.flydean.KeyObject.<init>() line: 11
    com.flydean.TestMemoryLeak.main(String[]) line: 17
  ]
}

lastKnownHeapUsage 是 heap 的应用大小,从日志中咱们能够看到这个值是始终在减少的。

allocationTime 示意的是这个对象调配的工夫。

startTime 示意的是这个对象被 dump 的工夫。

object 示意的是调配的对象。

stackTrace 示意的是这个对象被调配的 stack 信息。

留神,如果须要展现 stackTrace 信息,须要开启 -XX:StartFlightRecording:settings=profile 选项。

从下面的日志咱们能够剖析得出,main 办法中的第 17 行,也就是 KeyObject keyObject= new KeyObject(); 在一直的创立新的对象。

从而咱们能够进行更深层次的剖析,最终找到内存泄露的起因。

总结

本文通过 JFR 和 JMC 的应用,介绍了如何剖析内存泄露。心愿大家可能喜爱。

本文作者:flydean 程序那些事

本文链接:http://www.flydean.com/jvm-diagnostic-memory-leak/

本文起源:flydean 的博客

欢送关注我的公众号: 程序那些事,更多精彩等着您!

退出移动版