简介:
Java可以自动管理内存的回收,程序员不需要调用类似free(),delete()的函数释放内存,但是不意味着Java不存在内存泄漏问题。Java的内存的回收是由GC自动完成,但是GC不是万能的,当在GC能力范围之外的时候,内存泄漏就可能出现了。那什么时候超出了GC的能力范围?首先我们先了解GC能做什么,简而言之,GC可以回收的是不再被引用的对象内存,如果对象在引用,就不会回收其内存。反过来思考一下,如果有仍然在引用,但是实际程序中没有起作用的对象,这时候GC就无能为力了,也就意味着内存泄漏发生了。
另外一方面,Java虚拟机在GC时与应用程序是互斥的。也就意味着当gc回收内存,会阻止所有其他线程访问程序的内存空间,这时候整个应用处于停滞状态。有个形象的术语称这段gc时间为“stop-the-world”。一般情况下,这段时间短,不易察觉。但是当内存消耗过快,gc频繁进行时,或者gc时间较长时,程序的性能会受到明显影响。
原理:
那么gc的频率是由什么控制的,什么时候会发生gc?这里要涉及到java虚拟机的内存分配策略,一般情况,Java虚拟机(包括sun的,ibm的等等)内存采用分代管理策略,即将内存分为年轻代、年老代和永久代,每个代上对象的生命周期不同,并执行不同的回收算法。新创建的对象在年轻代,当年轻代内存用完时,这时仍然存活“老”对象被复制到年老代,同时对年轻代进行更新,这称之为一次minor collection,也就是发生了一次gc,又叫做Scavenge GC。以此类推,当年老代的内存分完时,仍然存活的对象被复制到永久代中,这称之为major collection,也是一次gc,又或者叫做full gc。Full gc会对整个堆进行整理,效率要低的多。
检测:
内存泄漏如何侦测?现在市面有很多工具,这里不介绍这些工具的使用,大家可以到网上去搜。其实现在大多数版本的jvm都自带了gc日志,可以通过分析日志观察gc的频率和内存的消耗来推断出内存泄漏的可能性。不过gc日志格式是不一致的,例如sun和ibm中gc日志格式就大不相同,sun的日志更加规范,可以方便通过程序来解析日志。这里以sun jdk1.5版本为例,看看如何设置gc监控:
1. 首先要启动gc监控,需要在java启动参数中增加-verbose:gc
2. 将日志输出到文件,需要增加参数-Xloggc:filename(例如-Xloggc:$BASE_HOME/logs/gc.log)。可以根据需要通过参数输出不同的gc信息。例如-XX:+PrintHeapAtGC -包括gc前后heap状况。其他的参数就不再介绍,可以上网去看。
这时大家可以就可以到logs下查看gc的日志了。
毕竟文件里都是数据,很难看出规律行,一般有专门的gc日志分析工具,以图形化的方式展现数据。例如:
这是一个简单的full gc走势图,应该是excel做的。从图中可以看出full gc后的内存明显越来越高,full gc的频率越来越快,消耗的时间也越来越长。说明既有可能发生了内存泄漏。如果要再做定位,还需要结合jprofiler之类的工具定位到具体代码。
Gc的工具有个不好的地方就是,因为gc日志的格式有很多,就是同样版本的jvm,不同的参数配置也会导致gc日志大不相同。一般的工具都过于局限,经常解析不了文件。可以自己写一个gc日志分析工具。