简介
尽管 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 的博客
欢送关注我的公众号: 程序那些事,更多精彩等着您!