在默认情况下,通过System.gc()或者Runtime.getRuntime().gc()的调用,会显式触发Full GC。 同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。
然而 System.gc()调用并不能保证马上对垃圾收集器的调用。
public class Test1 {
public static void main(String[] args) {
new Test1();
System.gc();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("调用了finalize");
}
}
通过之前的章节我们知道, 如果触发了垃圾回收,就会调用一次重写的finalize方法。我们执行main方法,却发现有时可以输出调用了finalize,有时候不能输出,也就验证了我们虽然调用了System.gc(),但是不能保证证马上对垃圾收集器的调用。
public class Test1 {
public static void main(String[] args) {
new Test1();
System.gc();
// 强制调用使用引用的对象finalize方法。
System.runFinalization();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("调用了finalize");
}
}
如上的代码,当我们加上System.runFinalization();每次都可以调用finalize方法,runFinalization方法可以强制调用使用引用的对象finalize方法。
通过查看System.gc()的代码:
public static void gc() {
Runtime.getRuntime().gc();
}
说明System.gc()和Runtime.getRuntime().gc()是等价的。
JVM实现者可以通过System.gc()调用来决定JVM的GC行为,而一般情况下,垃圾回收应该是自动进行的,无须手动触发,否则就太麻烦了。在一些特殊情况下,如我们正在编写一个性能基准,我们可以在运行之间调用System.gc()。
内存溢出是引发程序崩溃的罪魁祸首之一。由于GC一直在发展,所以一般情况下,除非应用程序占用的内存增长速度非常快,造成垃圾回收已经跟不上内存消耗的速度,否则不太容易出现OOM的情况。
大多数情况下,GC会进行各种年龄段的垃圾回收,实在不行了就放大招,来一次独占式的full gc操作,这时候会回收大量的内存,供应用程序仅需使用。
Javadoc对OOM的解释是,没有空闲内存,并且垃圾收集器也无法提供更多内存。
首先说没有空闲内存的情况:说明Java虚拟机的堆内存不够。原因有二:
再抛出OOM之前,通常垃圾收集器会被触发,尽可能的去清理空间。比如在引用机制分析中,涉及到JVM回去尝试回收软引用指向的对象等,在java.nio.Bits.reserveMemory()方法中,我们能清楚的看到,System.gc()会被调用,以清理空间。
当然,也不时在任何情况下垃圾收集器都会被触发,如果我们分配的是一个超大对象。已经超过堆的最大值,JVM可以判断出垃圾收集并不能解决这个问题,所以直接抛出OOM。
内存泄漏也称作存储泄漏。严格来说,只有对象不再被程序用到了,但是GC不能回收他们的情况才叫做内存泄漏。
但是实际情况很多时候一些不太好的实践或疏忽会导致对象的生命周期变得很长甚至导致OOM,也可以叫做宽泛意义上的内存泄漏。
尽管内存泄漏不会立刻引起程序崩溃,但是一旦发生内存泄漏,程序中的可用内存就会逐步蚕食,直至耗尽所有内存,最终出现OOM异常,导致程序崩溃。
如上图,红框部分是有一个指针没有断开,导致这些对象都不会被回收。
开发中内存泄漏的例子