结合代码和内存变化图一步步弄懂JVM的FullGC

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 一步步结合代码去验证jvm的内存变化,并画出内存变化的示意图,从而探索出jvm fullGC的原因。

1.年轻代存活的对象太多,老年代了放不下

01.示例代码

public class DemoTest1 {
    public static void main(String[] args) {
        byte[] array1 = new byte[4 * 1024 * 1024];
        array1 = null;

        byte[] array2 = new byte[2 * 1024 * 1024];
        byte[] array3 = new byte[2 * 1024 * 1024];
        byte[] array4 = new byte[2 * 1024 * 1024];
        byte[] array5 = new byte[128 * 1024];

        byte[] array6 = new byte[2 * 1024 * 1024];

    }

02.启动JVM参数

-XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8  -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log

其中,参数-XX:PretenureSizeThreshold,参数要设置大对象阈值为3MB,也就是超过3MB,就直接进入老年代。

大对象大小是3MB。一旦对象大小超过3MB,不会进入新生代,直接进入老年代。

启动命令:

java  -jar -XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8  -XX:MaxTenuringThre
shold=15 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log jvm-demo.jar

03.GC日志

启动之后就得到如下GC日志:

Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep  5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16703268k(7458748k free), swap 23781156k(9784196k free)
CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=3145728 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
0.174: [GC (Allocation Failure) 0.174: [ParNew (promotion failed): 7457K->8328K(9216K), 0.0046949 secs]
0.179: [CMS: 8194K->6962K(10240K), 0.0033396 secs] 11553K->6962K(19456K), [Metaspace: 2970K->2970K(1056768K)], 0.0089224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 par new generation   total 9216K, used 2130K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  26% used [0x00000000fec00000, 0x00000000fee14930, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 concurrent mark-sweep generation total 10240K, used 6962K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 330K, capacity 386K, committed 512K, reserved 1048576K

04.分析GC日志

先看如下代码:

 byte[] array1 = new byte[4 * 1024 * 1024];
        array1 = null;

这行代码直接分配了一个4MB的大对象,此时这个对象会直接进入老年代,接着array1不再引用这个对象。

此时内存分配如下:

1714d334d0287653~tplv-t2oaga2asx-image.image

紧接着就是如下代码

byte[] array2 = new byte[2 * 1024 * 1024];
        byte[] array3 = new byte[2 * 1024 * 1024];
        byte[] array4 = new byte[2 * 1024 * 1024];
        byte[] array5 = new byte[128 * 1024];

连续分配了4个数组,其中3个是2MB的数组,1个是128KB的数组,如下图所示,全部会进入Eden区域中。

1714d334d5fe9940~tplv-t2oaga2asx-image.image

接着会执行如下代码:byte[] array6 = new byte[2 * 1024 * 1024];。此时还能放得下2MB的对象吗?

不可能了,因为Eden区已经放不下了。因此此时会直接触发一次Young GC。

我们看下面的GC日志:

0.174: [GC (Allocation Failure) 0.174: [ParNew (promotion failed): 7457K->8328K(9216K), 0.0046949 secs]

这行日志显示了,Eden区原来是有7000多KB的对象,但是回收之后发现一个都回收不掉,因为上述几个数组都被变量引用了。

所以此时,一定会直接把这些对象放入到老年代里去,但是此时老年代里已经有一个4MB的数组了,还能放的下3个2MB的数组和1个128KB的数组吗?

明显是不行的,此时一定会超过老年代的10MB大小。

所以此时看gc日志:

0.179: [CMS: 8194K->6962K(10240K), 0.0033396 secs] 11553K->6962K(19456K), [Metaspace: 2970K->2970K(1056768K)], 0.0089224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 

此时执行了CMS垃圾回收器的Full GC,Full GC其实就是会对老年代进行Old GC,同时一般会跟一次Young GC关联,还会触发一次元数据区(永久代)的GC。

在CMS Full GC之前,就已经触发过Young GC了,此时可以看到此时Young GC就已经有了,接着就是执行针对老年代的Old GC,也就是如下日志:

CMS: 8194K->6962K(10240K), 0.0033396 secs

这里看到老年代从8MB左右的对象占用,变成了6MB左右的对象占用,这是怎么个过程呢?

很简单,一定是在Young GC之后,先把2个2MB的数组放入了老年代,如下图。

1714d334db4b93cd~tplv-t2oaga2asx-image.image

此时要继续放1个2MB的数组和1个128KB的数组到老年代,一定会放不下,所以此时就会触发CMS的Full GC。

然后此时就会回收掉其中的一个4MB的数组,因为他已经没人引用了,如下图所示。

1714d33522f05bd3~tplv-t2oaga2asx-image.image

所以再看CMS的垃圾回收日志:CMS: 8194K->6962K(10240K), 0.0033396 secs,他是从回收前的8MB变成了6MB,就是上图所示。

最后在CMS Full GC执行完毕之后,其实年轻代的对象都进入了老年代,此时最后一行代码要在年轻代分配2MB的数组就可以成功了,如下图。

1714d334d436fd46~tplv-t2oaga2asx-image.image

05.总结

这是一个触发老年代GC的案例,就是年轻代存活的对象太多放不下老年代了,此时就会触发CMS的Full GC。

2.老年代可用空间小于了历次Young GC后升入老年代的对象的平均大小

01.示例代码

public class DemoTest1 {
    public static void main(String[] args) {
        byte[] array1 = new byte[1 * 1024 * 1024];
        array1 = null;
        byte[] array2 = new byte[1 * 1024 * 1024];
        array2 = null;
        byte[] array3 = new byte[1 * 1024 * 1024];
        array3 = null;
        byte[] array4 = new byte[1 * 1024 * 1024];//触发YGC 1MB    1

        array1 = new byte[1 * 1024 * 1024];
        array1 = null;
        array2 = new byte[1 * 1024 * 1024];
        array2 = null;
        array3 = new byte[1 * 1024 * 1024];//触发YGC   Y 1MB O 1MB  2
        array3 = null;

        byte[] array5 = new byte[1 * 1024 * 1024];// Y 2MB  O 1MB
        array1 = new byte[1 * 1024 * 1024];// Y 3MB
        array1 = null;
        array2 = new byte[1 * 1024 * 1024];// Y 1MB  O 2MB YGC   3
        
        array2 = null;
        array3 = new byte[1 * 1024 * 1024];//Y 2MB  O 2MB
        array3 = null;
        byte[] array6 = new byte[1 * 1024 * 1024];//Y 3MB  O 2MB
        array1 = new byte[1 * 1024 * 1024];//Y 1MB  O 3MB YGC  4
        
        array1 = null;
        array2 = new byte[1 * 1024 * 1024];//Y 2MB
        array2 = null;
        array3 = new byte[1 * 1024 * 1024];//Y 3MB
        array3 = null;
        byte[] array7 = new byte[1 * 1024 * 1024];//YGC  5

    }
}

02.启动JVM参数

-XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8  -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=2M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log

其中,参数-XX:PretenureSizeThreshold,参数要设置大对象阈值为2MB,也就是超过2MB,就直接进入老年代。

大对象大小是3MB。一旦对象大小超过3MB,不会进入新生代,直接进入老年代。

启动命令:

java  -jar -XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8  -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=2M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log jvm-demo.jar

03.GC日志

启动之后就得到如下GC日志:

老年代

年轻代

Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep  5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16703268k(7221016k free), swap 23781156k(8613656k free)
CommandLine flags: -XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=5242880 -XX:MaxTenuringThreshold=15 -XX:NewSize=5242880 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=2097152 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
0.121: [GC (Allocation Failure) 0.121: [ParNew: 3155K->512K(4608K), 0.0041165 secs] 3155K->766K(9728K), 0.0042644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.125: [GC (Allocation Failure) 0.125: [ParNew: 3663K->0K(4608K), 0.0016667 secs] 3917K->1732K(9728K), 0.0017448 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.127: [GC (Allocation Failure) 0.127: [ParNew: 3142K->0K(4608K), 0.0013221 secs] 4875K->2756K(9728K), 0.0013592 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.129: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2756K(5120K)] 4878K(9728K), 0.0004498 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.129: [CMS-concurrent-mark-start]
0.130: [GC (Allocation Failure) 0.130: [ParNew: 3146K->0K(4608K), 0.0005869 secs] 5902K->2756K(9728K), 0.0006362 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.131: [GC (Allocation Failure) 0.131: [ParNew: 3148K->0K(4608K), 0.0007974 secs] 5904K->3780K(9728K), 0.0008262 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 par new generation   total 4608K, used 2207K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
  eden space 4096K,  53% used [0x00000000ff600000, 0x00000000ff827f38, 0x00000000ffa00000)
  from space 512K,   0% used [0x00000000ffa80000, 0x00000000ffa80000, 0x00000000ffb00000)
  to   space 512K,   0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)
 concurrent mark-sweep generation total 5120K, used 3780K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 330K, capacity 386K, committed 512K, reserved 1048576K

04.分析GC日志

(1).代码块1

先看如下代码:

  byte[] array1 = new byte[1 * 1024 * 1024];
  array1 = null;
  byte[] array2 = new byte[1 * 1024 * 1024];
  array2 = null;
  byte[] array3 = new byte[1 * 1024 * 1024];
  array3 = null;
  byte[] array4 = new byte[1 * 1024 * 1024];

这段代码直接分配了4个1MB的数组,并且在第4个数组的时候,会因为新生代内存不足触发YGC。

此时内存分配如下:

1714d334ea2345b2~tplv-t2oaga2asx-image.image

对应如下GC日志:

0.121: [GC (Allocation Failure) 0.121: [ParNew: 3155K->512K(4608K), 0.0041165 secs] 3155K->766K(9728K), 0.0042644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

此时,可以看到新生代就只剩512K的对象,这个奇怪的512KB的对象进入Survivor From区。

那么大小为1MB的数组对象去哪里呢?肯定不是这个奇怪的512KB的对象。

这1MB的数组首先肯定是准备进入Survivor From区,可是,在我们设置的JVM参数下,只有0.5MB,明显是不够分配的。根据JVM YoungGC的规则,Survivor区放不下GC之后存活的对象,直接进入老年代

所以,1MB的数组对象是直接进入到老年代了。

此时,内存分配如下:

1714d3362aa43a2a~tplv-t2oaga2asx-image.image

(2).代码块2

紧接这就是这块代码:

 array1 = new byte[1 * 1024 * 1024];
 array1 = null;
 array2 = new byte[1 * 1024 * 1024];
 array2 = null;
 array3 = new byte[1 * 1024 * 1024];

这里再次创建了3个1MB的数组对象,并且会触发一次YoungGC;

对应 GC日志如下:

0.125: [GC (Allocation Failure) 0.125: [ParNew: 3663K->0K(4608K), 0.0016667 secs] 3917K->1732K(9728K), 0.0017448 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

此时,Young GC之后,新生代变成0KB,那么存活的大小为1MB的数组对象去哪里呢?

这1MB的数组首先肯定是准备进入Survivor From区,可是,在我们设置的JVM参数下,只有0.5MB,明显是不够分配的。根据JVM YoungGC的规则,Survivor区放不下GC之后存活的对象,直接进入老年代

所以,1MB的数组对象是直接进入到老年代了。

之前看到的未知的对象512KB也进入到老年代,此时内存分配如下:

1714d33675a8e01c~tplv-t2oaga2asx-image.image

(3).代码块3
array3 = null;
byte[] array5 = new byte[1 * 1024 * 1024];
array1 = new byte[1 * 1024 * 1024];
array1 = null;
array2 = new byte[1 * 1024 * 1024];

这里再次创建了3个1MB的数组对象,并且会触发一次YoungGC;

对应的GC日志如下:

0.127: [GC (Allocation Failure) 0.127: [ParNew: 3142K->0K(4608K), 0.0013221 secs] 4875K->2756K(9728K), 0.0013592 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

此时内存分配如下:

1714d3363298497c~tplv-t2oaga2asx-image.image

(4).代码块4
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 2MB  O 2MB
array3 = null;
byte[] array6 = new byte[1 * 1024 * 1024];
array1 = new byte[1 * 1024 * 1024];

这里再次创建了3个1MB的数组对象,并且会触发一次YoungGC;并且在这儿,触发Young GC之前触发了一次CMS的Old GC,触发的条件就是老年代可用空间小于了历次Young GC后升入老年代的对象的平均大小。此时新生代大小变成0KB

对应的GC日志如下:

0.129: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2756K(5120K)] 4878K(9728K), 0.0004498 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.129: [CMS-concurrent-mark-start]
0.130: [GC (Allocation Failure) 0.130: [ParNew: 3146K->0K(4608K), 0.0005869 secs] 5902K->2756K(9728K), 0.0006362 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

此时内存分配如下:

1714d3367c1a0bc9~tplv-t2oaga2asx-image.image

(5).代码块5
array1 = null;
array2 = new byte[1 * 1024 * 1024];//Y 2MB
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 3MB
array3 = null;
byte[] array7 = new byte[1 * 1024 * 1024];

此时,再创建3个1MB的数组对象,再次触发一次Young GC,执行完YoungGC,此时新生代大小变成0KB;

对应的GC日志如下:

0.131: [GC (Allocation Failure) 0.131: [ParNew: 3148K->0K(4608K), 0.0007974 secs] 5904K->3780K(9728K), 0.0008262 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

此时内存分配如下:

1714d3369e58a00b~tplv-t2oaga2asx-image.image

(6).总结

如下GC堆内存日志我们也可以去验证下上面的推测:

此时新生代使用了53%的大小,我们还有一个1MB的数组,可能还存在一些未知对象。

在老年代中使用了大约3MB的空间,应该就是上图中的对象。

Heap
 par new generation   total 4608K, used 2207K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
  eden space 4096K,  53% used [0x00000000ff600000, 0x00000000ff827f38, 0x00000000ffa00000)
  from space 512K,   0% used [0x00000000ffa80000, 0x00000000ffa80000, 0x00000000ffb00000)
  to   space 512K,   0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)
 concurrent mark-sweep generation total 5120K, used 3780K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 330K, capacity 386K, committed 512K, reserved 1048576K

3.几个触发Full GC的条件

第一:是老年代可用内存小于新生代全部对象的大小,如果没开启空间担保参数,会直接触发Full GC,所以一般空间担保参数都会打开;注:jDK1.8之后已经取消了 -XX:-HandlePromotionFailure 机制

第二:是老年代可用内存小于历次新生代GC后进入老年代的平均对象大小,此时会提前Full GC;

第三:是新生代Minor GC后的存活对象大于Survivor,那么就会进入老年代,此时老年代内存不足。

上述情况都会导致老年代Full GC。

第四:就是“-XX:CMSInitiatingOccupancyFaction”参数,

如果老年代可用内存大于历次新生代GC后进入老年代的对象平均大小,但是老年代已经使用的内存空间超过了这个参数指定的比例,也会自动触发Full GC。默认92%

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4
|
8天前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
6天前
|
Java Linux Windows
JVM内存
首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制。
8 1
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
64 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
25天前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
46 10
|
22天前
|
存储 JavaScript 前端开发
如何优化代码以避免闭包引起的内存泄露
本文介绍了闭包引起内存泄露的原因,并提供了几种优化代码的策略,帮助开发者有效避免内存泄露问题,提升应用性能。
|
24天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
1月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
50 2
|
1月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
53 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
1月前
|
Java
JVM进阶调优系列(5)CMS回收器通俗演义一文讲透FullGC
本文介绍了JVM中CMS垃圾回收器对Full GC的优化,包括Stop the world的影响、Full GC触发条件、GC过程的四个阶段(初始标记、并发标记、重新标记、并发清理)及并发清理期间的Concurrent mode failure处理,并简述了GC roots的概念及其在GC中的作用。