JVM技术之旅-深入分析GC回收机制

简介: JVM技术之旅-深入分析GC回收机制

前提概要


GC的出现解放了程序员需要手动回收内存的苦恼,但我们也是要了解GC的,知己知彼,百战不殆嘛。




背景介绍


常见的GC回收算法主要包括引用计数算法、可达性分析法、标记清除算法、标记复制算法、标记压缩算法、分代算法以及分区算法


其中,引用计数法和可达性分析法用于判定一个对象是否可以回收,其他的算法为具体执行GC时的算法。


聊聊标记清除算法、复制算法、标记压缩算法、分代算法,主要介绍分代算法。 引用计数法和可达性分析法请移步: 引用计数法 可达性分析法




标记复制算法


标记复制算法会将内存空间分为两块,每次只使用其中一块内存。复制算法同样使用可达性分析法标记除垃圾对象,当GC执行时,会将可以存活的对象复制到另一块内存空间中,并且保证内存上的连续性,然后直接清空之前使用的内存空间。然后如此往复。我们姑且将这两块内存区域称为from区和to区。




标记阶段


如下图所示,r1和r2作为GC Root对象,经过可达性分析后,标记除黄色对象为垃圾对象。

image.png


复制阶段


复制过程如下,GC会将五个存活对象复制到to区,并且保证在to区内存空间上的连续性。


image.png


最后,将from区中的垃圾对象清除。

image.png



综上述,该算法在存货对象少,垃圾对象多的情况下,非常高效。其好处是不会产生内存碎片,但坏处也是显而易见的,就是直接损失了一半的可用内存。(jvm算法只是浪费了百分之10)



标记清除算法


标记清除法是现在GC算法的基础,目前似乎没有哪个GC还在使用这种算法了。因为这种算法会产生大量的内存碎片。


标记清除算法的执行过程分为两个阶段:标记阶段、清除阶段。
复制代码


  • 标记阶段会通过可达性分析将活跃的对象标记出来。


  • 清除阶段会将标记阶段标记的垃圾对象清除。



标记阶段如图所示:

image.png

  • Java堆中,黄色对象为不可达对象,在标记阶段被标记。


下面执行回收算法,执行后如图:


image.png


从上图可以清晰的看出此算法的缺陷,回收后会产生大量不连续的内存空间,即内存碎片。由于Java在分配内存时通常是按连续内存分配,那么当碎片空间不足以分配给新的对象时,就造成了内存浪费。



标记压缩算法


标记压缩算法可以解决标记清除算法的内存碎片问题。
复制代码




其算法可以看作三步:


  1. 标记垃圾对象


  1. 清除垃圾对象


  1. 内存碎片整理


其过程如下:


首先标记除垃圾对象(黄色)


image.png



清除垃圾对象

image.png



内存碎片整理

image.png



分代算法


分代算法基于复制算法和标记压缩算法


  • 首先,标记清除算法、复制算法、标记压缩算法都有各自的缺点,如果单独用其中某一算法来做GC,会有很大的问题


  • 例如,标记清除算法会产生大量的内存碎片,复制算法会损失一半的内存,标记压缩算法的碎片整理会造成较大的消耗


  • 其次,复制算法和标记压缩算法都有各自适合的使用场景。复制算法适用于每次回收时,存活对象少的场景,这样就会减少复制量标记压缩算法适用于回收时,存活对象多的场景,这样就会减少内存碎片的产生,碎片整理的代价就会小很多


分代算法将内存区域分为两部分:新生代和老年代


根据新生代和老年代中对象的不同特点,使用不同的GC算法。
复制代码


  • 新生代对象的特点是:创建出来没多久就可以被回收(例如虚拟机栈中创建的对象,方法出栈就会销毁)。也就是说,每次回收时,大部分是垃圾对象,所以新生代适用于复制算法。


  • 老年代的特点是:经过多次GC,依然存活。也就是说,每次GC时,大部分是存活对象,所以老年代适用于标记压缩算法。


新生代分为eden区、from区、to区,老年代是一整块内存空间,如下所示:


image.png

分代算法执行过程


首先简述一下新生代GC的整个过程(老年代GC会在下面介绍):新创建的对象总是在eden区中出生,当eden(到达阈值)时,会触发Minor GC,此时会将eden区中的存活对象复制到from和to中一个没有被使用的空间中,假设是to区(正在被使用的from区中的存活对象也会被复制到to区中)。


有几种情况,对象会晋升到老年代:


  1. 超大对象会直接进入到老年代(受虚拟机参数-XX:PretenureSizeThreshold参数影响,默认值0,即不开启,单位为Byte,例如:3145728=3M,那么超过3M的对象,会直接晋升老年代)


  1. 如果to区已满,多出来的对象也会直接晋升老年代


  1. 复制15次(15岁)后,依然存活的对象,也会进入老年代


此时eden区和from区都是垃圾对象,可以直接清除



PS:为什么复制15次(15岁)后,被判定为高龄对象,晋升到老年代呢? 因为每个对象的年龄是存在对象头中的,对象头用4bit存储了这个年龄数,而4bit最大可以表示十进制的15,所以是15岁。


下面从JVM启动开始,描述GC的过程。


JVM刚启动并初始化完成后,几块内存空间分配完毕,此时状态如上图所示。
复制代码


新创建的对象总是会出生在eden区

image.png


当eden区满的时候,会触发一次Minor GC,此时会从from和to区中找一个没有使用的空间,将eden区中还存活的对象复制过去(第一次from和to都是空的,使用from区,以后每一次都采用to区存储传递的数据对象),被复制的对象的年龄会+1,并清除eden区中的垃圾对象。


image.png


程序继续运行,又在eden区产生了新的对象,并产生了一个超大对象。

image.png

当eden区再次被填满时,会再一次触发Minor GC,这次GC会将eden区和from区中存活的对象复制到to区,并且对象年龄+1,超大对象会直接晋升到老年代,to区放不下的对象也会直接晋升老年代。


image.png


程序继续运行,假设经过15次复制,某一对象依然存活,那么他将直接进入老年代。

image.png



老年代 Full GC


在进行Minor GC之前,JVM还有一步操作,他会检查新生代所有对象使用的总内存是否小于老年代最大剩余连续内存,如果上述条件成立,那么这次Minor GC一定是安全的,因为即使所有新生代对象都进入老年代,老年代也不会内存溢出。


如果上述条件不成立,JVM会查看参数HandlePromotionFailure[1]是否开启(JDK1.6以后默认开启),如果没开启,说明Minor GC后可能会存在老年代内存溢出的风险,会进行一次Full GC如果开启,JVM还会检查历次晋升老年代对象的平均大小是否小于老年代最大连续内存空间,如果成立会尝试直接进行Minor GC,如果不成立,老年代执行Full GC。


image.png

eden区和from区的存活对象会复制到to区,超大对象和to区容纳不下的对象会直接晋升老年代。当eden区满时,触发Minor GC,此时判断老年代剩余连续内存已经小于新生代所有对象占用内存总和,假设HandlePromotionFailure参数开启,JVM还会继续判断老年代剩余连续内存是否大于历次晋升老年代对象的平均大小,如图所示,目前老年代还剩2个空间,如果之前平均每次晋升三个对象到老年代,剩余空间小于平均值,会触发Full GC。


老年代回收-标记:

image.png


老年代回收-清除:

image.png


老年代回收-碎片整理:

image.png



Minor GC存在的问题


Minor GC的问题在于,新生代的对象可能被老年代引用,而这种情况可达性分析是分析不到的,但这种情况的新生代对象是不应该被回收的


HotSpot虚拟机提供了一个解决方案:卡表。
复制代码


这种方法会将老年代内存平均分为很多的卡片,每个卡片都包含一部分对象,然后维护一个卡表,卡表是一个数组,每个元素指向一个卡片,并标识出这个卡片中有没有指向新生代的对象,如果有标识为1这样一来,Minor GC只需要扫描卡表中标识为1的卡片即可,大大提升了效率。


卡表如下图所示:

image.png


















相关文章
|
30天前
|
Java 关系型数据库 MySQL
【JVM】JDBC案例打破双亲委派机制
【JVM】JDBC案例打破双亲委派机制
28 4
|
8天前
|
算法 Java
Java垃圾回收(Garbage Collection,GC)是Java虚拟机(JVM)的一种自动内存管理机制,用于在运行时自动回收不再使用的对象所占的内存空间
【6月更文挑战第18天】Java的GC自动回收内存,包括标记清除(产生碎片)、复制(效率低)、标记整理(兼顾连续性与效率)和分代收集(区分新生代和老年代,用不同算法优化)等策略。现代JVM通常采用分代收集,以平衡性能和内存利用率。
34 3
|
15天前
|
运维 Java Shell
手工触发Full GC:JVM调优实战指南
本文是关于Java应用性能调优的指南,重点介绍了如何使用`jmap`工具手动触发Full GC。Full GC是对堆内存全面清理的过程,通常在资源紧张时进行以缓解内存压力。文章详细阐述了Full GC的概念,并提供了两种使用`jmap`触发Full GC的方法:通过`-histo:live`选项获取存活对象统计信息,或使用`-dump`选项生成堆转储文件以分析内存状态。同时,文中也提醒注意手动Full GC可能带来的性能开销,建议在生产环境中谨慎操作。
|
23小时前
|
存储 缓存 算法
JVM对象创建与内存分配机制
该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
5 0
|
1月前
|
存储 算法 Java
JVM(垃圾回收机制 --- GC)
JVM(垃圾回收机制 --- GC)
42 5
|
30天前
|
Java
【JVM】双亲委派机制、打破双亲委派机制
【JVM】双亲委派机制、打破双亲委派机制
23 1
|
9天前
|
存储 Java 关系型数据库
实时计算 Flink版操作报错合集之JVM Metaspace不回收并在任务取消后仍然持续增长直至耗尽,是什么导致的
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
26天前
|
存储 安全 Java
深入探究Java虚拟机(JVM)的技术细节
深入探究Java虚拟机(JVM)的技术细节
|
1月前
|
算法 Java
深入浅出JVM(十六)之三色标记法与并发可达性分析
深入浅出JVM(十六)之三色标记法与并发可达性分析
|
1月前
|
监控 Java Linux
JVM工作原理与实战(三十七):Shenandoah GC和ZGC
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了垃圾回收器的技术演进、Shenandoah GC、ZGC等内容。
32 0