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


















相关文章
|
2月前
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
3月前
|
监控 算法 Java
jvm-48-java 变更导致压测应用性能下降,如何分析定位原因?
【11月更文挑战第17天】当JVM相关变更导致压测应用性能下降时,可通过检查变更内容(如JVM参数、Java版本、代码变更)、收集性能监控数据(使用JVM监控工具、应用性能监控工具、系统资源监控)、分析垃圾回收情况(GC日志分析、内存泄漏检查)、分析线程和锁(线程状态分析、锁竞争分析)及分析代码执行路径(使用代码性能分析工具、代码审查)等步骤来定位和解决问题。
|
4月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
174 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
3月前
|
存储 监控 Java
JVM进阶调优系列(8)如何手把手,逐行教她看懂GC日志?| IT男的专属浪漫
本文介绍了如何通过JVM参数打印GC日志,并通过示例代码展示了频繁YGC和FGC的场景。文章首先讲解了常见的GC日志参数,如`-XX:+PrintGCDetails`、`-XX:+PrintGCDateStamps`等,然后通过具体的JVM参数和代码示例,模拟了不同内存分配情况下的GC行为。最后,详细解析了GC日志的内容,帮助读者理解GC的执行过程和GC处理机制。
|
4月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
182 3
|
4月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
162 3
|
4月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
78 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
4月前
|
Java
JVM进阶调优系列(5)CMS回收器通俗演义一文讲透FullGC
本文介绍了JVM中CMS垃圾回收器对Full GC的优化,包括Stop the world的影响、Full GC触发条件、GC过程的四个阶段(初始标记、并发标记、重新标记、并发清理)及并发清理期间的Concurrent mode failure处理,并简述了GC roots的概念及其在GC中的作用。
|
3月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
632 1
|
2月前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型