gpt4 book ai didi

06、调优实战 - GC日志分析1(模拟对象进入老年代)

转载 作者:大佬之路 更新时间:2024-01-23 20:33:47 24 4
gpt4 key购买 nike

1. 对象进入老年代的条件

在前面的文章中,已经整理过了很多次 对象进入老年代的时机了,在这里就再次进行巩固,并用代码模拟对象进入老年代并分析GC日志。

对象进入老年代的时机:

分配担保规则:新生代GC过后,存活对象太多,Survivor区放不下了,这个时候就需要通过分配担保进入老年代;

达到年龄阈值:对象在新生代熬过了15次(-XX:MaxTenuringThreshold)GC,达到了年龄阈值,会晋升到老年代;(这种对象一般很少,只有在系统中的确需要长期存在的核心组件等,它们一般不能被回收)

动态年龄判断:在新生代Survivor区的对象,如果:年龄1 + 年龄2 + 年龄3 + 年龄N 的对象大小占比大于了Survivor区的50%以上,那年龄N及以上的对象就会晋升到老年代;

大对象直接进入老年代;(-XX:PretenureSizeThreshold)

默认值为0,当不主动设置值时,不管多大的对象都会先在新生代分配内存;

当手动设置了这个值时,如果生成一个大于这个大小的对象(比如一个超大的数组或者其他对象),就会直接在老年代中为这个对象分配内存;

G1收集器中有专门的大对象Region,大对象不存在老年代;

2. 代码模拟-对象进入老年代

2.1 通过分配担保规则进入老年代

JVM运行参数

-Xmx20m -Xms20m -Xmn10m -XX:SurvivorRatio=8  -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=10m 
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:log/demo1.log

示例代码:

public class Demo1 {

	public static void main(String[] args) {
		byte[] array1 = new byte[2 * 1024 * 1024];
		array1 = new byte[2 * 1024 * 1024];
		array1 = new byte[2 * 1024 * 1024];

		byte[] array2 = new byte[128 * 1024];
		array2 = null;

		byte[] array3 = new byte[2 * 1024 * 1024];
	}
}

这篇文章中就不再对代码执行流程和GC日志做很详细的分析了,详细的分析步骤可以参考上一篇文章,这里就只对重点进行分析。

2.1.1 代码执行流程

首先在代码中创建了三个2MB的byte数组对象放到Eden区,并且array1最终只指向了最后一个对象,也就是说这个时候前面两个2MB的对象已经成为了垃圾对象,但是最后一个2MB的对象还存在引用;

然后又创建了一个128KB的byte数组对象放到Eden区,并且也把array2指向了null,这个时候这个128KB的对象也成为了垃圾对象;

再继续创建一个2MB对象,但是这个时候,还能成功创建吗?
来看JVM参数,-Xmn10m -XX:SurvivorRatio=8,新生代10M,按照比例Eden区有8M的内存空间;我们前面创建了3个2M的对象,和一个128K的对象(这个小对象的作用就是在这里,超过8M,其实不要也可以的,因为会有一些元数据对象),再加上这里想要创建的array3的2MB的对象,已经超过了Eden区的8M;

所以这里不能直接创建,需要先进行一次新生代GC(Young GC)。

2.1.2 GC日志分析

Java HotSpot(TM) 64-Bit Server VM (25.181-b13) for linux-amd64 JRE (1.8.0_181-b13), built on Jul  7 2018 00:56:38 by "java_re" with gcc 4.3.0 20080428 (Red Hat 4.3.0-8)
Memory: 4k page, physical 1863076k(242112k free), swap 2097148k(2097148k free)
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.045: [GC (Allocation Failure) 0.045: [ParNew: 6651K->264K(9216K), 0.0093427 secs] 6651K->2314K(19456K), 0.0094041 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
Heap
 par new generation   total 9216K, used 2476K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  27% used [0x00000000fec00000, 0x00000000fee290e0, 0x00000000ff400000)
  from space 1024K,  25% used [0x00000000ff500000, 0x00000000ff5422d0, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 concurrent mark-sweep generation total 10240K, used 2050K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2474K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 267K, capacity 386K, committed 512K, reserved 1048576K

[ParNew: 6651K->264K(9216K), 0.0093427 secs] 6651K->2314K(19456K):

264K:本次GC过后,新生代还有264K的对象,但是我们的代码中还有一个 array1指向的2M的对象是存活的,不应该被垃圾回收掉,到哪里去了呢?

根据前面的JVM参数,Survivor区只有1M的大小,这里存活的对象大小为2M,所以Survivor区并不能存放这个对象;所以它应该通过分配担保,进入老年代;

程序运行结束时:

  • par new generation total 9216K, used 2476K:

  • 2476K:新生代使用了2476K,它等于下面的两个之和;

  • eden space 8192K, 27% used:

  • Eden区使用了:8192K * 27% = 2211K;

  • from space 1024K, 25% used:

  • Survivor from区使用了:1024K * 25% = 256K;(Eden区的2211K + 25K = 新生代的 2467K)

  • 这里也说明了,在发生分配担保需要把存活对象放到老年代的时候,不是把所有的对象都放进老年代,也会有部分对象留在Survivor区;

  • concurrent mark-sweep generation total 10240K, used 2050K:

  • CMS管理的老年代区域使用了 2050K,也就是 array1指向的2M的存活对象,进入到了老年代;

这样也就验证了,分配担保规则:JVM中发生新生代GC时,如果Survivor区放不下存活对象的时候,会通过分配担保进行老年代;并且不是全部放入老年代,而是部分留在Survivor区,部分进入老年代

2.2 达到年龄阈值进入老年代

JVM运行参数

-Xmx20m -Xms20m -Xmn10m -XX:SurvivorRatio=8  -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=10m 
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:log/demo2.log

示例代码:

public class Demo2 {

	public static void main(String[] args) {
		byte[] array1 = new byte[128 * 1024];

		int count = 16;
		while (count >= 0) {
			byte[] array2 = new byte[2 * 1024 * 1024];
			byte[] array3 = new byte[2 * 1024 * 1024];
			byte[] array4 = new byte[2 * 1024 * 1024];
			count--;
		}
	}
}

2.2.1 代码执行流程

首先创建了一个array1指向的128M的byte数组对象,这个对象一直被array1引用,不会成为垃圾对象;

然后在一个while循环里面,每次循环都创建 3个2M的对象;由于while循环也是以栈帧的方式处理,所以当出了此次循环之后,这6M的对象都会变成垃圾对象;

JVM参数:-Xmn10m -XX:SurvivorRatio=8,新生代10M的内存空间,按照比例Eden内存空间8M;

每在下一次进入while循环时,由于新生代已经存在了6M(还有些元数据对象)的对象,不能再继续创建2M的对象了,所以需要进行新生代GC。

2.2.2 GC日志分析

Java HotSpot(TM) 64-Bit Server VM (25.181-b13) for linux-amd64 JRE (1.8.0_181-b13), built on Jul  7 2018 00:56:38 by "java_re" with gcc 4.3.0 20080428 (Red Hat 4.3.0-8)
Memory: 4k page, physical 1863076k(220764k free), swap 2097148k(2097148k free)
CommandLine flags: -XX:CMSInitiatingOccupancyFraction=70 -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.058: [GC (Allocation Failure) 0.058: [ParNew: 6651K->392K(9216K), 0.0036491 secs] 6651K->392K(19456K), 0.0037028 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
0.063: [GC (Allocation Failure) 0.063: [ParNew: 6698K->405K(9216K), 0.0003712 secs] 6698K->405K(19456K), 0.0003906 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.064: [GC (Allocation Failure) 0.064: [ParNew: 6693K->469K(9216K), 0.0003293 secs] 6693K->469K(19456K), 0.0003395 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.064: [GC (Allocation Failure) 0.064: [ParNew: 6762K->407K(9216K), 0.0003119 secs] 6762K->407K(19456K), 0.0003247 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.065: [GC (Allocation Failure) 0.065: [ParNew: 6704K->392K(9216K), 0.0003626 secs] 6704K->392K(19456K), 0.0003756 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.066: [GC (Allocation Failure) 0.066: [ParNew: 6691K->388K(9216K), 0.0002962 secs] 6691K->388K(19456K), 0.0003057 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.066: [GC (Allocation Failure) 0.066: [ParNew: 6689K->387K(9216K), 0.0003149 secs] 6689K->387K(19456K), 0.0003485 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.067: [GC (Allocation Failure) 0.067: [ParNew: 6689K->387K(9216K), 0.0002938 secs] 6689K->387K(19456K), 0.0003027 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.067: [GC (Allocation Failure) 0.067: [ParNew: 6690K->387K(9216K), 0.0002991 secs] 6690K->387K(19456K), 0.0003108 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.068: [GC (Allocation Failure) 0.068: [ParNew: 6690K->387K(9216K), 0.0003484 secs] 6690K->387K(19456K), 0.0003609 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.068: [GC (Allocation Failure) 0.068: [ParNew: 6690K->387K(9216K), 0.0002949 secs] 6690K->387K(19456K), 0.0003041 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.069: [GC (Allocation Failure) 0.069: [ParNew: 6690K->387K(9216K), 0.0003131 secs] 6690K->387K(19456K), 0.0003263 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
0.070: [GC (Allocation Failure) 0.070: [ParNew: 6690K->387K(9216K), 0.0002947 secs] 6690K->387K(19456K), 0.0003039 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.070: [GC (Allocation Failure) 0.070: [ParNew: 6690K->387K(9216K), 0.0003084 secs] 6690K->387K(19456K), 0.0003199 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.071: [GC (Allocation Failure) 0.071: [ParNew: 6690K->387K(9216K), 0.0002944 secs] 6690K->387K(19456K), 0.0003034 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.071: [GC (Allocation Failure) 0.071: [ParNew: 6691K->0K(9216K), 0.0007872 secs] 6691K->392K(19456K), 0.0007992 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
 par new generation   total 9216K, used 6467K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  78% used [0x00000000fec00000, 0x00000000ff250fa0, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 concurrent mark-sweep generation total 10240K, used 392K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2475K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 267K, capacity 386K, committed 512K, reserved 1048576K

  • [ParNew: 6651K->392K(9216K), 0.0036491 secs]:

  • 在每下次进入while循环时,Eden区不够再分配2M对象时,执行的新生代GC;

  • 392K:新生代存活对象,也就是array1执行的128K的存活对象,和一些元数据对象;

  • [ParNew: 6698K->405K(9216K), 0.0003712 secs]:

  • 同上,一直执行15次;

  • [ParNew: 6691K->0K(9216K), 0.0007872 secs]:

这里是第16次执行新生代GC;

0K:说明在这次GC执行之后,新生代中没有存活对象了,那array1指向的存活对象和一些元数据对象到哪去了呢?

程序运行结束时:

  • concurrent mark-sweep generation total 10240K, used 392K:

  • used 392K:老年代中多出了 392K的对象;

  • 这也就是上面在新生代中连续15次存活,在第16次消失不见了的对象;

这样也就验证了,达到年龄阈值:对象在新生代熬过了15次(-XX:MaxTenuringThreshold)GC,达到了年龄阈值,会晋升到老年代

2.3 动态年龄判断进入老年代

JVM运行参数

-Xmx20m -Xms20m -Xmn10m -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=10m 
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:log/demo3.log

示例代码:

public class Demo3 {

	public static void main(String[] args) {
		// 第一阶段
		byte[] array1 = new byte[2 * 1024 * 1024];
		array1 = new byte[2 * 1024 * 1024];
		array1 = new byte[2 * 1024 * 1024];
		array1 = null;

		byte[] array2 = new byte[2 * 128 * 1024];

		// GC
		byte[] array3 = new byte[2 * 1024 * 1024];

		// 第二阶段
		array3 = new byte[2 * 1024 * 1024];
		array3 = new byte[2 * 1024 * 1024];
		array3 = null;

		byte[] array4 = new byte[3 * 128 * 1024];

		// GC
		byte[] array5 = new byte[2 * 1024 * 1024];

		// 第三阶段
		array5 = new byte[2 * 1024 * 1024];
		array5 = new byte[2 * 1024 * 1024];
		array5 = null;

		// GC
		byte[] array6 = new byte[2 * 1024 * 1024];
	}
}

2.3.1 代码执行流程

JVM参数:-Xmn10m -XX:SurvivorRatio=8,新生代10M,按比例Eden区8M,单个Survivor区1M;

  • 第一阶段:

  • 连续创建了3个2M的byte数组对象,并把array1最终指向null,即这6M对象都是垃圾对象了;

  • 再创建了一个array2指向的256K的对象,且一直被array2引用;

  • 在继续创建array3指向的2M的对象的时候,Eden区不够了,需要进行一次新生代GC;

  • 第二阶段:

  • 在第一阶段的GC过后,新生代还存活了:array2指向的256K的对象、 array3指向的2M的对象(这个会随着代码继续运行,不会在第一次GC日志中展示);

  • 继续创建2个2M的对象,并把array3指向null,即这6M对象都是垃圾对象了;

  • 再创建了一个array4指向的384K的对象,且一直被array4引用;

  • 在继续创建array5指向的2M的对象的时候,Eden区不够了,需要再进行一次新生代GC;

  • 第三阶段:

  • 在第二阶段的GC过后,新生代还存活了:array2指向的256K的对象、array4指向的384K的对象、array5指向的2M的对象(这个会随着代码继续运行,不会在第一次GC日志中展示);

  • 再继续创建2个2M的对象,并把array5指向null,即这6M对象也都成为了垃圾对象了;

  • 在继续创建array6指向的2M的对象的时候,同样Eden区不够了,需要进行一个新生代GC;

  • 这个时候,新生代本该还存活:array2指向的256K的对象、array4指向的384K的对象、array6指向的2M的对象

2.3.2 GC日志分析

Java HotSpot(TM) 64-Bit Server VM (25.181-b13) for linux-amd64 JRE (1.8.0_181-b13), built on Jul  7 2018 00:56:38 by "java_re" with gcc 4.3.0 20080428 (Red Hat 4.3.0-8)
Memory: 4k page, physical 1863076k(241792k free), swap 2097148k(2097148k free)
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.042: [GC (Allocation Failure) 0.042: [ParNew: 6651K->392K(9216K), 0.0104425 secs] 6651K->392K(19456K), 0.0104896 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
0.053: [GC (Allocation Failure) 0.053: [ParNew: 7082K->789K(9216K), 0.0004104 secs] 7082K->789K(19456K), 0.0004275 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.055: [GC (Allocation Failure) 0.055: [ParNew: 7078K->384K(9216K), 0.0008941 secs] 7078K->776K(19456K), 0.0009134 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
 par new generation   total 9216K, used 2596K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  27% used [0x00000000fec00000, 0x00000000fee290e0, 0x00000000ff400000)
  from space 1024K,  37% used [0x00000000ff500000, 0x00000000ff560010, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 concurrent mark-sweep generation total 10240K, used 392K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2475K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 267K, capacity 386K, committed 512K, reserved 1048576K

  • [ParNew: 6651K->392K(9216K), 0.0104425 secs] 6651K->392K(19456K), 0.0104896 secs]:

  • 第一阶段发生的GC;

  • 392K:本次GC过后,新生代存活的 array2指向的256K对象 + 100多K的元数据对象;

  • 此时他们年龄1岁

  • [ParNew: 7082K->789K(9216K), 0.0004104 secs] 7082K->789K(19456K), 0.0004275 secs]:

  • 第二阶段发生的GC;

  • 789K:本次GC过后,新生代存活的 array2指向的256K对象 + array4指向的384K对象 + 100多K的元数据对象;

  • 此时上面的392K对象年龄2岁,789K - 392K = 397K 对象的年龄1岁

  • [ParNew: 7078K->384K(9216K), 0.0008941 secs] 7078K->776K(19456K), 0.0009134 secs]

  • 第三阶段发生的GC;

  • 384K:本次GC过后,新生代存活的对象大小;但是这里为什么比上一次的 789K还少了呢?

  • 此时注意到,第二次GC过后,新生代存活了 789K的对象,存活对象大小已经超过了Survivor的50%;

  • 根据动态年龄判断:1岁(397K)+ 2岁(392K) 超过了 50%,所以需要把2岁的对象晋升到老年代,即把 392K的晋升到老年代;

程序运行结束时:

  • concurrent mark-sweep generation total 10240K, used 392K:

  • CMS管理的老年代有 392K的对象,也就是从新生代晋升上来的2岁的对象;

这样也就验证了,动态年龄判断:在新生代Survivor区的对象,如果:年龄1 + 年龄2 + 年龄3 + 年龄N 的对象大小占比大于了Survivor区的50%以上,那年龄N及以上的对象就会晋升到老年代

2.4 大对象直接进入老年代

JVM运行参数:

-Xmx10m -Xms10m -Xmn5m -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=1m 
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:log/demo4.log

示例代码:

public class Demo4 {

	public static void main(String[] args) {
		byte[] array1 = new byte[2 * 1024 * 1024];
	}
}

这里的代码流程没有什么值得分析的,就是直接 创建一个2M的byte数组对象,但是这里由于手动指定了大对象的JVM参数:-XX:PretenureSizeThreshold=1m为1M,所以这个2M的对象超过了这个大对象阈值。

2.4.1 GC日志分析

Java HotSpot(TM) 64-Bit Server VM (25.181-b13) for linux-amd64 JRE (1.8.0_181-b13), built on Jul  7 2018 00:56:38 by "java_re" with gcc 4.3.0 20080428 (Red Hat 4.3.0-8)
Memory: 4k page, physical 1863076k(242176k free), swap 2097148k(2097148k free)
CommandLine flags: -XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=5242880 -XX:MaxTenuringThreshold=15 -XX:NewSize=5242880 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=1048576 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
Heap
 par new generation   total 4608K, used 508K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
  eden space 4096K,  12% used [0x00000000ff600000, 0x00000000ff67f190, 0x00000000ffa00000)
  from space 512K,   0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)
  to   space 512K,   0% used [0x00000000ffa80000, 0x00000000ffa80000, 0x00000000ffb00000)
 concurrent mark-sweep generation total 5120K, used 2048K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2474K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 267K, capacity 386K, committed 512K, reserved 1048576K

在这里只创建了一个 2M的对象,不会发生GC,所以日志中也没有GC相关的;

par new generation total 4608K, used 508K:

程序执行完成后,新生代中只有 508K的对象;

但是我们创建了一个 2M的对象,说明没有在新生代中;

程序运行结束时:

concurrent mark-sweep generation total 5120K, used 2048K:

  • CMS管理的老年代中有了 2048K的对象,也就是我们创建的那个2M的对象;
  • 说明由于设置了大对象阈值:-XX:PretenureSizeThreshold=1m,这个超过阈值的大对象,直接进入了老年代;

这样也就验证了,大对象直接进入老年代:当手动设置了阈值时,如果生成一个大于这个大小的对象(比如一个超大的数组或者其他对象),就会直接在老年代中为这个对象分配内存

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com