关于java:JVM对象进入老年代的四种方式4实战篇

7次阅读

共计 3122 个字符,预计需要花费 8 分钟才能阅读完成。

对象进入老年代的四种形式

  • minor gc 后,survivor 区空间不能包容全副存活对象
  • 存活对象达到年龄阈值。比方 15
  • 大对象
  • 动静年龄判断

动静年龄判断

首先,咱们还是先理解一下什么是动静年龄判断?

书本的解释如下:

总结一下:就是说 survivor 区中,如果雷同年龄的所有对象大小所占用的空间大于 survivor 空间的一半,年龄大于或等于该年龄对象的,都能够间接进入老年代。

这是书本的说法。。

但其实这个说法是谬误的。

置信看过我另一篇文章 JVM- 动静年龄判断的小伙伴,是理解正确的说法应该是: 在 survivor 区中,所有年龄的对象的所占空间的累加和大于 survivor 空间的一半,大于或等于该年龄的对象,都能够进入老年代。

接下来咱们间接上代码和 JVM 配置参数:

JVM 配置参数:

-XX:NewSize=10m -XX:MaxNewSize=10m -XX:InitialHeapSize=20m -XX:MaxHeapSize=20m -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=10m -XX:MaxTenuringThreshold=15 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:dynamicheck.log

代码:

咱们先间接把代码跑起来,而后间接看日志文件:

Java HotSpot(TM) 64-Bit Server VM (25.281-b09) for bsd-amd64 JRE (1.8.0_281-b09), built on Dec  9 2020 12:44:49 by "java_re" with gcc 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5)
Memory: 4k page, physical 16777216k(106332k free)

/proc/meminfo:

CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 

0.117: [GC (Allocation Failure) 0.118: [ParNew: 7115K->619K(9216K), 0.0029447 secs] 7115K->619K(19456K), 0.0033789 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.122: [GC (Allocation Failure) 0.122: [ParNew: 7223K->0K(9216K), 0.0020215 secs] 7223K->601K(19456K), 0.0020509 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 par new generation   total 9216K, used 2212K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  27% used [0x00000007bec00000, 0x00000007bee290e0, 0x00000007bf400000)
  from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
 concurrent mark-sweep generation total 10240K, used 601K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
 Metaspace       used 2713K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 291K, capacity 386K, committed 512K, reserved 1048576K

局部代码分析:

当初间断调配了 3 个 2M 的对象,和 1 个 300K 的对象,最初还把 array1 置为 null。此时的堆图应该是这样子的

接下来,还要持续调配一个 2M 的对象,这个时候 eden 区还能持续调配空间吗?

必定不能够,因为 eden 区只有 8M。

那这个时候,只好执行 young gc 来清理空间了。

接着,咱们看一下日志文件:

ParNew: 7115K->619K(9216K)

阐明 GG 前,占用了 7115K,这里大略包含 3 个 2m 对象 +300k 对象 + 几百 K 未知对象。通过 GC 后,只剩下 619K 对象,包含 300K 对象和未知对象。

而后给新创建的 2m 对象调配到 eden 区

此时,咱们得察看一个重点:就是 from 区是 1m, 就是 1024K。当初呢,有 619K 对象曾经来到了 from 区,是超过 from 区的一半的。

接着咱们持续看代码:

执行完这个代码。会在堆里会新增 2 个 2m 对象,1 个 300K 对象,最初 array3 置为 null。

如果要继续执行 byte[] array4 = new byte[2*_1MB];

那么 eden 区就不够空间了。这个时候会触发第二次 young gc 了。

咱们持续看一下第二次 young gc 的日志:

ParNew: 7223K->0K(9216K)

阐明了啥?

GC 前,一共应用了 7223K,包含 eden 区的 3 个 2m 对象 +300K 对象和 from 区的 300K 对象 + 未知对象,GC 后,整个新生代都有空了。

实践上,GC 后,array2 还援用着 300K 对象的。所以,能够必定的是,这 300K 对象,必定不会被回收。

但当初 GC 日志显著通知咱们,新生代在 GC 后的空间使用率为 0。

这是为什么呢?

不焦急,咱们持续看一下老年代的空间日志:

concurrent mark-sweep generation total 10240K, used 601K

你看,老年代居然被应用了 601K。其实这 601K,就是 300K 对象和未知对象的空间。

为什么它们会在老年代?

就是因为触发了动静年龄判断呀。

你想想,首先,300K 不是大对象吧。(-XX:PretenureSizeThreshold=10m)

也没达到 15 岁,因为才 young gc 两次。(-XX:MaxTenuringThreshold=15)

而且当初 survivor 是 1024K 空间,是足以包容 601K 的存活对象的。所以,这些都不是导致对象进入老年代的起因。

当初因为 2 次 young gc,那 600K 对象都存活着,并且占用的空间是超过 survivor 区的空间大小一半。从而触发动静空间判断,进入老年代。

说到这里,置信大家应该都能明确了。如果还不明确,欢送加大头菜微信一起探讨。

正文完
 0