还有程序员搞不懂JVM垃圾回收器并发标记清除回收的内存管理?

简介: JVM中从JDK 4正式引入并发回收,用于解决垃圾回收过程中停顿时间过长的问题。JVM的垃圾回收器通常采用分代设计,新生代和老生代采用不同的垃圾回收算法,在并发垃圾回收器中,新生代采用并行的复制算法,老生代采用并发的标记清除算法

并发标记清除回收

JVM中从JDK 4正式引入并发回收,用于解决垃圾回收过程中停顿时间过长的问题。JVM的垃圾回收器通常采用分代设计,新生代和老生代采用不同的垃圾回收算法,在并发垃圾回收器中,新生代采用并行的复制算法,老生代采用并发的标记清除算法。

狭义上所说的并发回收(Concurrent-MarkSweep,CMS)仅仅指针对老生代的回收,而广义上所说的并发垃圾回收指的是新生代采用并行复制算法、老生代采用并发标记清除算法。本文使用广义上的概念。

到目前为止,CMS仍然是最成功的垃圾回收器,满足了大多数业务场景的需要。一方面,其本身存在设计上的特点,存在停顿时间偶发过长的情况、停顿时间不可控等问题。另外一方面,CMS在实现时为了能更好地满足停顿时间的需要,将并发粒度设计得非常细,例如老生代垃圾回收的某些阶段可以和Mutator并发运行,老生代垃圾回收可以和新生代垃圾回收并发运行,新生代垃圾回收还可以抢占老生代垃圾回收等,细粒度的并发设计会导致CMS实现的复杂性,所以CMS也是bug最多的垃圾回收器之一,在使用时存在崩溃的情况。基于以上原因,JVM引入了Garbage First的垃圾回收器用于替代CMS,从JDK 11开始,正式不推荐使用CMS,在JDK 14中CMS正式被移除。

鉴于目前生产环境中仍然以JDK 8为主,同时广大的程序员仍然使用CMS,另外,大家遇到的如V8(JavaScript虚拟机)、ART(AndroidRuntime)等其他虚拟机中的垃圾回收也都有并发标记清除的垃圾回收器实现,所以本章还是对CMS进行详细介绍。

内存管理

为了解决垃圾回收过程中应用存在较长停顿时间的问题,CMS在吸收分代串行回收的优点的同时改进了原有垃圾回收器的不足之处(在CMS之前JVM还引入了火车回收算法,但该算法很快被移除,所以本书没有进一步介绍它)。CMS也采用分代的垃圾回收,保证应用有较高的吞吐量。同时它还对串行的分代回收做了优化,主要表现如下:

1)对新生代采用了并行的复制算法,提高了新生代的回收效率。

2)对老生代采用了并发标记清除垃圾回收算法,在垃圾回收过程仅回收死亡对象,然后重用死亡对象的内存空间,故此老生代回收有较低的停顿时间。

3)因为老生代中不移动活跃对象,所以在垃圾回收完成后存在较多的内存碎片,在应用运行一段时间后,可能无法响应应用的分配请求,因此又引入Full GC,针对整个堆空间进行回收。

和串行回收相比,CMS整个堆的管理示意图如图4-1所示。

网络异常,图片无法展示
|

图4-1 CMS堆管理示意图

CMS也采用边界固定的分代实现。实际上CMS的设计者也发现针对堆空间的划分不容易,所以在设计之初希望能支持新生代和老生代边界自由移动,实现新生代和老生代大小的动态调整,但是该思想在CMS中并未实现。所以CMS仍然采用固定边界的堆空间划分方法,将整个堆空间划分为两个代:新生代和老生代。

1)新生代主要用于响应Mutator的分配请求。

2)老生代主要用于新生代垃圾回收时对象的晋升,同时也可以响应一些特殊的Mutator分配请求(例如当Mutator请求对象过大时,就直接分配在老生代中)。

对于新生代的内存分配管理方式采用和串行的垃圾回收中类似的方式,为每一个Mutator关联一个TLAB,用于响应Mutator的分配。

对于老生代,因为采用并发标记清除算法,所以内存的管理方式稍微复杂,采用的是复杂链表的方式管理空间内存。最为简单的链表一般通过指针来管理空闲内存块,如图4-2所示。

网络异常,图片无法展示
|

图4-2 简单链表式内存管理示意图

简单的链表管理把所有空闲的内存块放在一条链表中(图4-2中采用大小不同的图例表示内存块大小不同),导致简单的链表管理存在性能问题,当Mutator或者GC线程分配内存时,需要遍历整个链表才能找到一个大小“合适”的内存块(避免内存的浪费),所以性能极其低下。对于这种管理方式一个简单的优化是:将大小相同的内存块放在同一个链表中,也就是说老生代被划分为多个链表,每个链表仅仅管理同样大小的内存块。这样在分配时可以根据请求的大小直接找到对应的链表来获取内存,如图4-3所示。

网络异常,图片无法展示
|

图4-3 多条链表管理不同大小的空闲内存块

该优化部分解决了查找内存块的问题,但是Mutator请求的对象大小分布可能相当广泛,可能有十几字节,也可能有几十千字节或者兆字节,需要很多链表来保存不同大小的内存块。而多个链表也会在查找链表头时存在性能问题(定位到某一个大小的链表)。所以进一步的优化是将所有的链表形成一棵二叉树,树中的每一个节点都是一个链表头,每个链表管理一个相同大小的内存块,如图4-4所示(图中使用实线描述二叉树的结构,用虚线描述树节点关联的链表)。

针对内存的请求,先查找树的节点,然后再查找树节点管理的链表进行分配。但在实现中还有一些细节需要考虑,比如树形链表的管理方式,需要使用额外的指针来构建树或者链表。如果使用额外的内存来管理这些指针,将会浪费一定的内存,所以CMS又对树形链表的结构进一步优化,消除了这些额外指针的内存消耗。

另外还有一点,虽然使用树形链表的方式管理空闲内存提高了分配的效率,但是每一次分配需要先查找树中的节点,再查找链表,分配效率仍然低下。另外,当树中找不到合适大小的内存块时,还需要对树的节点进行拆分用于满足分配,效率就更为低下。特别是针对一个小对象来说,这样的分配效率会直接影响Mutator和GC线程的吞吐量,所以还需要进一步优化。一个自然而然的方式是针对小对象使用额外的缓存方式,即图4-3所示的多链表管理方式。

CMS的老生代采用的就是如图4-3和图4-4所示的复合管理方式。

网络异常,图片无法展示
|

图4-4 树形内存管理方式

本文给大家讲解的内容是JVM垃圾回收器详解:并发标记清除回收,内存管理

相关文章
|
5月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
472 55
|
6月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
446 6
|
5月前
|
缓存 算法 Java
JVM深入原理(八)(一):垃圾回收
弱引用-作用:JVM中使用WeakReference对象来实现软引用,一般在ThreadLocal中,当进行垃圾回收时,被弱引用对象引用的对象就直接被回收.软引用-作用:JVM中使用SoftReference对象来实现软引用,一般在缓存中使用,当程序内存不足时,被引用的对象就会被回收.强引用-作用:可达性算法描述的根对象引用普通对象的引用,指的就是强引用,只要有这层关系存在,被引用的对象就会不被垃圾回收。引用计数法-缺点:如果两个对象循环引用,而又没有其他的对象来引用它们,这样就造成垃圾堆积。
151 0
|
5月前
|
算法 Java 对象存储
JVM深入原理(八)(二):垃圾回收
Java垃圾回收过程会通过单独的GC线程来完成,但是不管使用哪一种GC算法,都会有部分阶段需要停止所有的用户线程。这个过程被称之为StopTheWorld简称STW,如果STW时间过长则会影响用户的使用。一般来说,堆内存越大,最大STW就越长,想减少最大STW,就会减少吞吐量,不同的GC算法适用于不同的场景。分代回收算法将整个堆中的区域划分为新生代和老年代。--超过新生代大小的大对象会直接晋升到老年代。
105 0
|
7月前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
9月前
|
存储 设计模式 监控
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
923 166
|
11月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
1796 1
|
7月前
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
324 29
JVM简介—1.Java内存区域
|
7月前
|
存储 设计模式 监控
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
168 0
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
|
8月前
|
存储 算法 Java
JVM: 内存、类与垃圾
分代收集算法将内存分为新生代和老年代,分别使用不同的垃圾回收算法。新生代对象使用复制算法,老年代对象使用标记-清除或标记-整理算法。
103 6