简介

尽管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的博客

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