JVM GC原理深度解析及性能调优实战

JVM运行时内存

先看下拓扑图
在这里插入图片描述
Java堆从GC的角度还可以细分为: 新生代(Eden区、From Survivor区和To Survivor区)和老年代。
在这里插入图片描述
新生代:分三个区:一个Eden区,两个Survivor区(一般而言),大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区的存活且不满足“晋升”条件的对象将被复制到另外一个Survivor区。对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代,这个过程也称为“晋升”。显然,“晋升年龄阈值”的大小直接影响着对象在新生代中的停留时间,在Serial和ParNew GC两种回收器中,“晋升年龄阈值”通过参数MaxTenuringThreshold设定,默认值为15。

Eden区:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。

ServivorFrom:上一次GC的幸存者,作为这一次GC的被扫描者。

ServivorTo:保留了一次MinorGC过程中的幸存者。

老年代:主要存放应用程序中生命周期长的内存对象。

老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。

MajorGC采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常。

永久代:指内存的永久保存区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域,它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。

在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制。

简单描述下的gc 过程是这样的:
在这里插入图片描述
1、现在有一个新对象产生,那么对象一定需要内存空间,于是现在需要为该对象进行内存空间的申请。

2、首先会判断伊甸园区是否有内存空间,如果此时有充足内存空间,则直接将新对象保存到伊甸园区。

3、但是如果此时伊甸园区的内存空间不足,那么会自动执行Minor GC操作,将伊甸园区无用的内存空间进行清理。清理之后会继续判断伊甸园区空间是否充足?如果充足,则将新的对象直接在伊甸园区进行内存空间分配。

4、如果执行Minor GC之后伊甸园区空间依然不足,那么这个时候会进行存活区判断,如果存活区有剩余空间,则将伊甸园区的部分活跃对象保存在存活区,随后继续判断伊甸园区的内存空间是否充足,如果充足,则进行内存空间分配。

5、如果此时存活区也没有内存空间了,则继续判断老年区,如果此时老年区的空间充足,则将存活区中的活跃对象保存到老年区,而后存活区应付出现空余空间,随后伊甸园区将部分活跃对象保存地存活区中,最后在伊甸园区为新对象分配内存空间。

6、如果这个时候老年代内存空间也满了,那么这个时候将产生Major GC(Full GC)。然后再将存活区中的活跃对象保存到老年区,从而腾出空间,然后再将伊甸园区的部分活跃对象保存到存活区,最后在伊甸园区为新对象分配内存空间。

7、如果老年代执行Full GC之后依然空间依然不足,应付产生OOM(OutOfMemoryError)异常。

1public static void main(String[] args) { 2 Random random = new Random(); 3 String str = "show GC"; 4 while (true) { 5 str += str + random.nextInt(999); 6 str.intern(); // 强制产生垃圾 7 } 8} 9 10

内存大小最大设置为10M,方便观察,结果:

1[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->887K(9728K), 0.0008724 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 2[GC (Allocation Failure) [PSYoungGen: 2355K->504K(2560K)] 2738K->1275K(9728K), 0.0008424 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 3[GC (Allocation Failure) [PSYoungGen: 2433K->504K(2560K)] 3205K->2371K(9728K), 0.0005561 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 4[GC (Allocation Failure) [PSYoungGen: 2205K->504K(2560K)] 4072K->3642K(9728K), 0.0007300 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 5[GC (Allocation Failure) [PSYoungGen: 2206K->496K(2560K)] 7007K->6176K(9728K), 0.0006085 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 6[Full GC (Ergonomics) [PSYoungGen: 496K->0K(2560K)] [ParOldGen: 5680K->1842K(7168K)] 6176K->1842K(9728K), [Metaspace: 3539K->3539K(1056768K)], 0.0052348 secs] [Times: user=0.08 sys=0.00, real=0.01 secs] 7[Full GC (Ergonomics) [PSYoungGen: 871K->0K(2560K)] [ParOldGen: 6831K->5021K(7168K)] 7702K->5021K(9728K), [Metaspace: 3541K->3541K(1056768K)], 0.0057354 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 8[Full GC (Ergonomics) [PSYoungGen: 1743K->0K(2560K)] [ParOldGen: 6684K->4188K(7168K)] 8428K->4188K(9728K), [Metaspace: 3541K->3541K(1056768K)], 0.0041574 secs] [Times: user=0.09 sys=0.00, real=0.00 secs] 9[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 4188K->4188K(8704K), 0.0002199 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 10[Full GC (Allocation Failure) Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 11 at java.util.Arrays.copyOf(Arrays.java:3332) 12 at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) 13 at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448) 14 at java.lang.StringBuilder.append(StringBuilder.java:136) 15 at com.huoq.test.Test.main(Test.java:22) 16[PSYoungGen: 0K->0K(1536K)] [ParOldGen: 4188K->4168K(7168K)] 4188K->4168K(8704K), [Metaspace: 3541K->3541K(1056768K)], 0.0055203 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 17Heap 18PSYoungGen total 1536K, used 61K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000) 19eden space 1024K, 5% used [0x00000000ffd00000,0x00000000ffd0f498,0x00000000ffe00000) 20from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) 21to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) 22ParOldGen total 7168K, used 4168K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000) 23object space 7168K, 58% used [0x00000000ff600000,0x00000000ffa12140,0x00000000ffd00000) 24Metaspace used 3573K, capacity 4502K, committed 4864K, reserved 1056768K 25class space used 391K, capacity 394K, committed 512K, reserved 1048576K 26 27Process finished with exit code 1 28 29

可以看到年轻代空间不足触发Minor GC,多次Minor GC后空间仍然不足会触发老年代的空间回收,老年代空间分配失败会触发Full GC,多次Full GC之后空间仍然不足就会导致“OutOfMemoryErroy”。

代码交流 2021