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