个人随笔
目录
JVM(八)、用例子来测试JVM内存的分配情况以及Minor GC和Full GC的发生时机
2021-03-03 22:11:13

一、JVM内存分配情况

我们知道,Java内存分配的时候,堆中1/3是年轻代(新生代),2/3是老年代,我们现再设置如下JVM启动参数

  1. -Xms9M -Xmx9M -XX:+PrintGCDetails

然后运行代码

  1. public class HeapTest {
  2. public static void main(String[] args) {
  3. //堆内存占用测试
  4. }
  5. }

结果如下

  1. Heap
  2. PSYoungGen total 2560K, used 955K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  3. eden space 2048K, 46% used [0x00000000ffd00000,0x00000000ffdeec80,0x00000000fff00000)
  4. from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  5. to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  6. ParOldGen total 7168K, used 0K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  7. object space 7168K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffd00000)
  8. Metaspace used 2537K, capacity 4486K, committed 4864K, reserved 1056768K
  9. class space used 270K, capacity 386K, committed 512K, reserved 1048576K

我们的堆内存大概有9*1024=9216KB,然后新生代为(PSYoungGen)2560KB,老年代(ParOldGen)有7168K,占比跟上面说的1/3是年轻代,2/3是老年代差不多,然后元空间(Metaspace)用了2537K,元空间就是方法区,方法区用的内存是直接内存。

并且我们还可以知道,就算没有执行任何逻辑,堆中eden区还是会有一定的占用,那是因为HeadTest以及一些
JVM本来的类产生的对象。

二、测试JVM内存回收

大多数情况下,对象在年轻代中Eden区分配,当Eden区中没有足够的空间进行分配时,虚拟机将发起一次Minor GC,在老年代没有足够空间的时候才会进行一次Full GC,我们先看一下Minor GC和Full GC有啥区别:

Minor GC,在老年代没有足够空间的时候才会进行一次Full GC,我们先看一下Minor GC和Full GC有啥区别:
Minor GC/Young GC:指发生新生代的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。Minor GC/Young GC:指发生新生代的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。

Major GC/Full GC:一般会回收老年代,年轻代,方法区的垃圾,Major GC的速度一般会比Minor GC的慢10倍以上。

为什么Full GC会比Minor GC慢10倍以上,这里简单说明下,因为新生代垃圾回收一般用的算法时复制算法,而老年代一般用的时标记整理算法,所以比较慢,这个在以后会说到。

下面来触发一下垃圾回收,JVM配置参数和代码如下:

  1. -Xms9M -Xmx9M -XX:+PrintGCDetails

1、先分配1000KB内存

  1. public class HeapTest {
  2. public static void main(String[] args) {
  3. //1、先分配1000KB内存
  4. byte[] k1 = new byte[1000*1024];
  5. }
  6. }

运行结果如下

  1. Heap
  2. PSYoungGen total 2560K, used 1955K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  3. eden space 2048K, 95% used [0x00000000ffd00000,0x00000000ffee8ce8,0x00000000fff00000)
  4. from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  5. to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  6. ParOldGen total 7168K, used 0K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  7. object space 7168K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffd00000)
  8. Metaspace used 2537K, capacity 4486K, committed 4864K, reserved 1056768K
  9. class space used 270K, capacity 386K, committed 512K, reserved 1048576K

新生代:eden:95%;from:0%;to:0%;
老年代:0%;

2、再分配200KB内存

  1. public class HeapTest {
  2. public static void main(String[] args) {
  3. //1、先分配1000KB内存
  4. byte[] k1 = new byte[1000*1024];
  5. //2、再分配200KB内存
  6. byte[] k2 = new byte[200*1024];
  7. }
  8. }

运行结果:

  1. [GC (Allocation Failure) [PSYoungGen: 1913K->504K(2560K)] 1913K->1648K(9728K), 0.0018048 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  2. Heap
  3. PSYoungGen total 2560K, used 664K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  4. eden space 2048K, 7% used [0x00000000ffd00000,0x00000000ffd28370,0x00000000fff00000)
  5. from space 512K, 98% used [0x00000000fff00000,0x00000000fff7e010,0x00000000fff80000)
  6. to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  7. ParOldGen total 7168K, used 1144K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  8. object space 7168K, 15% used [0x00000000ff600000,0x00000000ff71e010,0x00000000ffd00000)
  9. Metaspace used 2538K, capacity 4486K, committed 4864K, reserved 1056768K
  10. class space used 270K, capacity 386K, committed 512K, reserved 1048576K

新生代:eden:10%;from:98%;to:0%;
老年代:15%;

此时因为Eden已经放不下了,所以触发了一次minor GC,然后将一部分内存移动到了from区,但是发现移动到from区后还是放不下,所以就移动到了老年代。

3、再分配2K内存

  1. public class HeapTest {
  2. public static void main(String[] args) {
  3. //1、先分配1000KB内存
  4. byte[] k1 = new byte[1000*1024];
  5. //2、再分配200KB内存
  6. byte[] k2 = new byte[200*1024];
  7. //3、再分配2KB内存
  8. byte[] k3 = new byte[2*1024];
  9. }
  10. }

运行结果如下

  1. [GC (Allocation Failure) [PSYoungGen: 1913K->504K(2560K)] 1913K->1608K(9728K), 0.0020718 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  2. Heap
  3. PSYoungGen total 2560K, used 766K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  4. eden space 2048K, 12% used [0x00000000ffd00000,0x00000000ffd41a00,0x00000000fff00000)
  5. from space 512K, 98% used [0x00000000fff00000,0x00000000fff7e030,0x00000000fff80000)
  6. to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  7. ParOldGen total 7168K, used 1104K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  8. object space 7168K, 15% used [0x00000000ff600000,0x00000000ff714010,0x00000000ffd00000)
  9. Metaspace used 2538K, capacity 4486K, committed 4864K, reserved 1056768K
  10. class space used 270K, capacity 386K, committed 512K, reserved 1048576K

新生代:eden:12%;from:98%;to:0%;
老年代:15%;

表明新分配的对象如果Eden区内存还足够的话,还是会在eden区分配内存的。

4、再分配8000KB内存,看是否会触发Full GC

  1. public class HeapTest {
  2. public static void main(String[] args) {
  3. //1、先分配1000KB内存
  4. byte[] k1 = new byte[1000*1024];
  5. //2、再分配200KB内存
  6. byte[] k2 = new byte[200*1024];
  7. //3、再分配2KB内存
  8. byte[] k3 = new byte[2*1024];
  9. //4、再分配8000KB内存
  10. byte[] k4 = new byte[8000*1024];
  11. }
  12. }

运行结果

  1. [GC (Allocation Failure) [PSYoungGen: 1913K->504K(2560K)] 1913K->1624K(9728K), 0.0024726 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  2. [GC (Allocation Failure) [PSYoungGen: 745K->496K(2560K)] 1865K->1828K(9728K), 0.0012383 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  3. [GC (Allocation Failure) [PSYoungGen: 496K->480K(2560K)] 1828K->1852K(9728K), 0.0006963 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  4. [Full GC (Allocation Failure) [PSYoungGen: 480K->0K(2560K)] [ParOldGen: 1372K->1772K(7168K)] 1852K->1772K(9728K), [Metaspace: 2531K->2531K(1056768K)], 0.0068447 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
  5. [GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 1772K->1772K(9728K), 0.0004366 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  6. [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 1772K->1761K(7168K)] 1772K->1761K(9728K), [Metaspace: 2531K->2531K(1056768K)], 0.0067124 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
  7. Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  8. at com.suibibk.jvm.HeapTest.main(HeapTest.java:13)
  9. Heap
  10. PSYoungGen total 2560K, used 60K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  11. eden space 2048K, 2% used [0x00000000ffd00000,0x00000000ffd0f1f8,0x00000000fff00000)
  12. from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  13. to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  14. ParOldGen total 7168K, used 1761K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  15. object space 7168K, 24% used [0x00000000ff600000,0x00000000ff7b8748,0x00000000ffd00000)
  16. Metaspace used 2563K, capacity 4486K, committed 4864K, reserved 1056768K
  17. class space used 273K, capacity 386K, committed 512K, reserved 1048576K

根据结果可以知道,先触发了Minor GC,后面老年代也不够了就触发了Full GC,并且抛出了OutOfMemoryError。为什么都触发了Full GC还会抛出OutOfMemoryError异常呢?

是因为对于强引用,当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

那么我们要换一种方法来测试了,我们知道Java有一种引用叫做软引用,来描述一些还有用但是并非必须的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题。

5、用软引用来测试Full GC并且不触发OutOfMemoryError。

因为我内存分配的总共就只有9M,所以测试值比较难找,如果对象分配内存太大,那么就算有软引用也还是不够。

不使用软引用的情况

  1. public class HeapTest {
  2. public static void main(String[] args) {
  3. //1、先分配1000KB内存
  4. //SoftReference<byte[]> k1 = new SoftReference<byte[]>(new byte[1000*1024]);
  5. byte[] k1 = new byte[1000*1024];
  6. //2、再分配200KB内存
  7. byte[] k2 = new byte[200*1024];
  8. //3、再分配2KB内存
  9. byte[] k3 = new byte[2*1024];
  10. //4、再分配6400KB内存
  11. byte[] k4 = new byte[6400*1024];
  12. }
  13. }

运行结果

  1. [GC (Allocation Failure) [PSYoungGen: 1913K->504K(2560K)] 1913K->1640K(9728K), 0.0016796 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  2. [GC (Allocation Failure) [PSYoungGen: 745K->488K(2560K)] 1881K->1872K(9728K), 0.0011202 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  3. [GC (Allocation Failure) [PSYoungGen: 488K->480K(2560K)] 1872K->1864K(9728K), 0.0008974 secs] [Times: user=0.03 sys=0.02, real=0.01 secs]
  4. [Full GC (Allocation Failure) [PSYoungGen: 480K->0K(2560K)] [ParOldGen: 1384K->1772K(7168K)] 1864K->1772K(9728K), [Metaspace: 2531K->2531K(1056768K)], 0.0065214 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  5. [GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 1772K->1772K(9728K), 0.0003934 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  6. [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 1772K->1761K(7168K)] 1772K->1761K(9728K), [Metaspace: 2531K->2531K(1056768K)], 0.0072175 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
  7. Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  8. at com.suibibk.jvm.HeapTest.main(HeapTest.java:17)
  9. Heap
  10. PSYoungGen total 2560K, used 60K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  11. eden space 2048K, 2% used [0x00000000ffd00000,0x00000000ffd0f1f8,0x00000000fff00000)
  12. from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  13. to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  14. ParOldGen total 7168K, used 1761K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  15. object space 7168K, 24% used [0x00000000ff600000,0x00000000ff7b8748,0x00000000ffd00000)
  16. Metaspace used 2562K, capacity 4486K, committed 4864K, reserved 1056768K
  17. class space used 273K, capacity 386K, committed 512K, reserved 1048576K

可以看到,触发了OutOfMemoryError。

然后使用软引用

  1. public class HeapTest {
  2. public static void main(String[] args) {
  3. //1、先分配1000KB内存
  4. SoftReference<byte[]> k1 = new SoftReference<byte[]>(new byte[1000*1024]);
  5. //byte[] k1 = new byte[1000*1024];
  6. //2、再分配200KB内存
  7. byte[] k2 = new byte[200*1024];
  8. //3、再分配2KB内存
  9. byte[] k3 = new byte[2*1024];
  10. //4、再分配6400KB内存
  11. byte[] k4 = new byte[6400*1024];
  12. }
  13. }

我们的预计是再Full GC后发现内存不够的情况下,会把软引用k1给回收,然后就不会导致OutOfMemoryError,运行测试看看

  1. [GC (Allocation Failure) [PSYoungGen: 1914K->504K(2560K)] 1914K->1640K(9728K), 0.0020576 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  2. [GC (Allocation Failure) [PSYoungGen: 745K->480K(2560K)] 1881K->1816K(9728K), 0.0012011 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  3. [GC (Allocation Failure) [PSYoungGen: 480K->496K(2560K)] 1816K->1848K(9728K), 0.0005865 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  4. [Full GC (Allocation Failure) [PSYoungGen: 496K->0K(2560K)] [ParOldGen: 1352K->1772K(7168K)] 1848K->1772K(9728K), [Metaspace: 2531K->2531K(1056768K)], 0.0065809 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
  5. [GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 1772K->1772K(9728K), 0.0003726 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  6. [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 1772K->762K(7168K)] 1772K->762K(9728K), [Metaspace: 2531K->2531K(1056768K)], 0.0060668 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  7. Heap
  8. PSYoungGen total 2560K, used 20K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  9. eden space 2048K, 1% used [0x00000000ffd00000,0x00000000ffd05360,0x00000000fff00000)
  10. from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  11. to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  12. ParOldGen total 7168K, used 7162K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  13. object space 7168K, 99% used [0x00000000ff600000,0x00000000ffcfe830,0x00000000ffd00000)
  14. Metaspace used 2537K, capacity 4486K, committed 4864K, reserved 1056768K
  15. class space used 270K, capacity 386K, committed 512K, reserved 1048576K

果然,跟预计的一样,没有导致OutOfMemoryError。

三、总结

  • 1、新对象会先放在新生代的Eden区
  • 2、再Eden区满了后会触发Minor GC
  • 3、如果新生代触发了Minor GC后还是放不下则会将对象放入老年代中
  • 4、如果老年代也满了就会触发Full GC
  • 5、触发Full GC的时候如果内存还不够并且没有软引用对象则会报OutOfMemoryError,若有软引用会把软引用强制清理,若清理后内存够就不会报OutOfMemoryError,否则还是会爆OutOfMemoryError。
 423

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2