System.gc()

Runs the garbage collector. Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse. When control returns from the method call, the Java Virtual Machine has made a best effort to reclaim space from all discarded objects. The call System.gc() is effectively equivalent to the call: Runtime.getRuntime().gc()

上面是 JDK8 对这个方法的说明,大概意思就是调用该方法会触发垃圾收集行为,下面强调几个点:

注意:可以通过System.gc()调用来决定 Java 虚拟机 GC 的行为;但一般情况下,垃圾回收应该是自动进行的!!

辨析点一

对于不同垃圾收集器,System.gc()的表现可能略有不同,测试代码如下所示:

本人电脑中 HotSpot 虚拟机使用的垃圾收集器是 UseParallelGC,如下所示:

使用 ParallelGC 输出的结果如下所示:

可以看到一共进行了两次 GC,分别是 Young GC 和 Full GC。原因如下:

关于这个内容的详细解释可见 Major GC和Full GC的区别是什么?触发条件呢? - RednaxelaFX的回答 - 知乎

如果我们使用参数-XX:+UseSerialGC,那么就只进了一次 Full GC,输出结果如下所示:

辨析点二

无论是使用 ParallelGC 还是使用 SerialGC,都会发现一个很奇怪的地方,慢慢道来...(注意:我们的分析基于ParallelGC)

根据虚拟机启动参数-Xms1024m -Xmx1024m可以知道为 Java 堆分配了固定的 1024m 的内存,利用jstat命令查看内存分配情况:

新生代和老年代的默认比例是 1 : 2,所以新生代和老年代的内存大小分别约为 298.5m : 683m (注意:新生代只算一个 Survivor 区的大小)

新生代中 Eden、s0、s1 的默认比例是 8 : 1 : 1,所以它们的内存大小分别约为 256m : 42.5m : 42.5m

上面具体把每个区的大小计算出来只是为了有个大小概念,有些误差没关系,只需要明确对于 10MB 大小的buffer数组存到任何区域都是绰绰有余的

首先buffer会在 Eden 区分配内存,当执行为一次 Young GC 后,buffer会被移动到 s0 区,但是当执行完 Full GC,新生代中的对象全部移动到了老年代

此时问题就来了,新生代晋升到老年代只有四种情况:(更详细的内容可见 对象的创建)

这个例子好像不满足上述四种中的任何一种情况,那为什么会在 Full GC 时将新生代中的对象全部移动到老年代呢?

HotSpot 的 Full GC 实现中,默认年轻代中所有活的对象都要晋升到老年代,实在晋升不了才会留在年轻代

注意:执行完 Full GC 后老年代内存空间的使用不减反增也是非常正常的行为。假如执行 Full GC 的时候,老年代中的对象几乎没有死掉的,而年轻代又有活对象晋升来,那么 Full GC 结束后老年代内存空间的使用自然就上升了

关于这个内容的详细解释可见 JVM full GC的奇怪现象,求解惑? - RednaxelaFX的回答 - 知乎