与C、C++等手动管理内存的编程语言不同,Java程序在运行时会自动对内存中不再使用的对象进行检测,并回收这部分对象所占用的内存空间。
在JDK中,JVM将堆(heap)内存分为了不同的区域块,每种区域会使用不同的垃圾收集器,每种垃圾收集器又会使用各自的垃圾收集算法。因此JVM的垃圾收集设计大致架构如下,从上至下越来越偏向于细节,本文使用自底向上的方式进行介绍
检测垃圾的方式一般有引用计数法和可达性分析两种,因为引用计数法无法检测到循环引用的垃圾对象,所以现在JVM中进行垃圾检测都是使用的可达性分析方法。
在检测到了垃圾对象之后,JVM就需要对这些垃圾对象进行垃圾回收(英语:Garbage Collection,缩写为GC),JVM中使用到的GC算法主要由如下三种
该算法分为标记和清除两个步骤,第一步标记是将所有活动的对象做上标记,第二部清除是将所有没有标记的对象进行回收。
标记压缩算法和标记清除算法基本上一致,只是标记清除算法过程只能单纯的把对象内存空间给释放出来,时间长了会造成内存碎片的问题。标记压缩算法在标记清除算法的基础上会对存活的对象进行移动,之后把这些对象向某一端进行移动来保证有完整的内存空闲空间。
标记压缩算法避免了内存碎片的问题,内存的利用率得到了提高,但是因为要移动对象所以它的GC效率要比标记清除算法要差。
复制算法将内存分为FROM和TO两块区域,对象在From区域内进行分配,之后对FROM区域进行GC,存活的对象被全部复制TO区域;之后对象在TO区域进行分配,GC发生在TO区域,GC后存活的对象被复制到FROM区域,如此反复。
在JVM中存在着多种垃圾收集器,这些垃圾收集器使用了一种或多种垃圾收集算法,我们可以认为垃圾收集算法是垃圾收集器的理论基础。
下面是JVM中几种常见的垃圾回收器
垃圾回收器 | 垃圾回收算法 |
---|---|
Serial | 复制算法 |
ParNew | 复制算法 |
Serial Old | 标记-整理 |
CMS | 标记-清除 |
G1 | 标记-整理+复制算法 |
堆是区别于栈、方法区等等的一大块内存空间,用于存放对象。JVM将堆内存再进行分区,分为新生代(Young Generation)和老年代(Old Generation),在新生代进行的GC叫做Young GC或Minor GC,在老年代进行的GC叫做Old GC或Major GC,其中新生代的内存又可以分为伊甸(Eden)和存活区(Survivor),Survivor分为两块,一般称为From和To。典型的对象分配和GC流程如下
上面介绍的是新生代的GC过程,当To区域存活的对象到达一定的年纪之后(每次GC的存活对象都会增加一岁),JVM就会将该对象移动到Old Generation。老年代的内存分配相较于新生代要简单的多,只有一块区域。
以JDK1.8中流行的 -XX:+UseConcMarkSweepGC
选项为例,该选项会在新生代使用ParNew垃圾收集器,在老年代使用CMS垃圾收集器。
与经典的堆内存不一样,G1垃圾收集器会将堆内存分为一个个的region区域,使用多个region来替代以前连续的堆内存空间。内存分块这种思想与Linux中的内存分页十分类似,本质都是把内存打散以方便进行管理,用离散的内存来替代连续的内存。内存分块之后可以从根本上解决内存碎片的问题,并且内存管理起来更加的简单。
G1的GC可以分为Young Only Phase、Mixed gc Phase和Full gc Phase。
对象会不断地分配到Eden分区中,Eden分区的数量会不断地增长,这个操作会一直进行下去直到Eden分区数量达到上限。
当Eden分区的数量达到了阈值之后,就会对所有的Eden分区进行GC,之后存活的对象会移动到Survivor区中,如果没有Survivor区则会随意挑选一个空闲的region作为Survivor并保存存活对象。
同时如果Survivor分区中的某些对象达到了一定年纪之后,这些对象也会被复制到Old分区中。
Old分区越来越大,当老年分区的占比达到了一定比例之后,就会触发针对年轻代和老年代的GC,是为Mixed gc。
在进行Mixed gc之前,先需要进行**并发标记周期(Concurrent Marking Cycle)**,这个过程会分为5步
到这里,G1的一个并发周期就算结束了,其实就是主要完成了垃圾定位的工作,定位出了哪些分区是垃圾最多的。因为整堆一般比较大,所以这个周期应该会比较长,中间可能会被多次STW的Young GC打断。
等到并发标记周期完成之后,就会进入Mixed gc Phase了,混合垃圾收集周期既会回收新生代的垃圾,也会回收老年代的垃圾。
当以下两个现象同时发生时就会触发Full GC
Full gc时单个线程会对整个堆的所有代中所有分区做标记、清除以及压缩动作,非常昂贵。
可以通过对JVM设置如下的Xlog来打印GC相关的日志以方便对JVM的GC过程进行深入的了解:
-Xlog:gc*:file=logs/gc.log:t,tags,level:filecount=5,filesize=100m
Elasticsearch对JDK 8u40之前的Java版本是不推荐使用G1垃圾收集器的,如果检测到错误的Java版本和GC配置,Elasticsearch会启动失败。但是在Elasticsearch 7.5.2自带的版本为13.0.1的bundled JDK中,G1已经成为了默认的垃圾收集器,G1已经通过了ES的测试验证并且ES团队也给出了一些关于Java堆(heap)内存管理的最佳实践。
咱们从头到尾说一次 Java 垃圾回收 - InfoQ
JVM中的垃圾回收策略
搞懂G1垃圾收集器 - 博客园
G1GC 概念与性能调优 - OPPO互联网技术
G1 垃圾收集器介绍 - Javadoop
Universal GC Log Analyzer
Java Hotspot G1 GC的一些关键技术 - 美团技术团队
新一代垃圾回收器ZGC的探索与实践 - 美团技术团队
Getting Started with the G1 Garbage Collector
Go 垃圾回收(三)——三色标记法是什么鬼?
V8 增量 GC 之三色标记
Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide
jdk11下g1收集器使用