### G1垃圾回收的过程
**dirty card queue:对于应用程序的引用赋值,如object1.field = object2,JVM会在之前和之后执行特殊的操作以在dirty card queue中入队一个保存了对象引用信息的card,在年轻代回收的时候,G1会对dirty card queue中所有card进行处理,以更新RSet,保证RSet实时准确的反应引用关系。****为什么不在赋值的时候直接更新RSet呢?这是为了不损失性能,RSet的处理需要线程同步,开销大,使用队列可以一定程度上缓解同步操作RSet带来的性能损失。**
* 年轻代GC
* **并行的、独占式的(STW发生)的垃圾回收**,可能会发生对象的代晋升,将会把对象放入Survivor或者是老年代。
* 当Eden区内存空间耗尽的时候,G1会启动一次年轻代垃圾回收,顺带回收Survivor区。
* G1 Young GC的时候,首先停止程序线程(STW),G1创建回收集(Collection set,需要被回收的内存分段集合,包括Eden区和Survivor区的所有内存分段)
* 第一阶段:扫描根(GC Roots),GC Roots联通RSet记录的外部引用作为扫描存活对象的入口。
* 第二阶段:处理**dirty card queue**中的card,更新RSet。此阶段完成后,**RSet可以准确的反应老年代对所在的内存分段中对象的引用**。
* 第三阶段:识别被老年代对象指向的Eden区中的对象,这些被指向的Eden中对象被认为是存活的对象。
* 第四阶段:复制对象(**使用的是复制算法**),对象数被遍历,Eden区region中存活的对象会被复制到Survivor区中的region,Survivor region中的存活对象如果年龄未达到阈值,年龄+1,达到阈值会被复制到Old region,如果Survivior的空间不够用,Eden中的部分数据会直接晋升到老年代。
* 第五阶段:处理Soft、Weak、Phantom、Final、JNI Weak等引用,最终Eden区的数据为空,这些空的region将会等待对象的分配,GC停止工作,目标内存中的对象也都是连续的,没有内存碎片。
* 老年代并发标记
* **当堆空间的内存占用达到阈值(-XX:InitiatingHeapOccupancyPercent,默认45%)就开始老年代的并发标记过程**。
* 初始标记阶段:**标记GC Roots直接可达的对象,也就是直接引用关系对象**,会发生STW(由于是直接可达的对象的标记,所以暂停时间很短),**并且会触发一次Young GC**
* 根区域的扫描(Root Region Scanning):G1扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象。这一个过程**必须在Young GC之前完成(因为Young GC会操作Survivor区中的对象)**。
* 并发标记(Concurrent Marking):在整个堆中进行并发标记(与程序线程并发执行),**此过程可能会被Young GC打断**,在并发标记阶段中,**若发现某些region中所有对象都是垃圾,那这个region就会被立即回收**,同时并发标记过程中,会**计算每个region的对象活性(该region存活对象的比例,G1垃圾回收的时候并不是所有region都会参与回收的,根据回收的价值高低来优先回收价值较高的region)**。
* 再次标记:由于并发标记阶段是收集器的标记线程和程序线程并发执行的,需要进行再次标记,修正上一次的标记结果,可以理解为增量补偿标记。会出现STW(暂停时间较短)。G1采用的是比CMS跟快的初始快照算法:snapshot-at-the-beginning(SATB)
* 独占清理:计算各个region的存活对象和GC回收比例,并进行排序(回收价值高低排序),识别可以混合回收的区域。为下阶段做铺垫,会发生STW。**需要注意的是这个阶段实际上并不会做垃圾的回**收。
* 并发清理阶段:识别并清理完成空闲的区域
* 混合回收
![](https://img.kancloud.cn/34/59/3459872f815223e9d48ac36d2ca540d5_584x475.png)
* 包括年轻代和老年代的垃圾回收
* 标记完成后马上开始垃圾的回收。对于一个混合的回收过程,G1从老年代移动存活的对象到空闲区域,这些空闲的区域变成了老年代region。当越来越多的对象晋升到老年代region的时候,为了避免堆内存被耗尽,就会触发以哦个混合垃圾收集Mixed GC,该算法并不是一个Old GC也不是Full GC,除了回收整个Young region之外,还会回收一部分Old region,**部分的region垃圾回收设计可以对垃圾回收的耗时进行控制**。
* 在并发标记结束之后,老年代中能够完全确认为垃圾的region中的内存分段被回收了,部分为垃圾的region中内存分段也被计算出来了,默认情况下,这些老年代的内存分段会被分为8次回收(**可以通过-XX:G1MixedGCCountTarget设置**)。
* 混合回收的回收集包括1/8的老年代的内存分段,Eden区内存分段,Survivor内存分段,**混合回收的算法和年轻代回收的算法完全一致**。
* 混合回收并不一定要进行8次,有一个**阈值设置:-XX:G1HeapWastePercent,默认值10%,代表允许整个堆内存中有10%的内存可以被浪费,意味着如果发现可以回收的垃圾占对内存的比例低于10%,则不进行混合回收**,因为GC花费的时间相对于较少的垃圾回收来说得不偿失。
* **由于老年代的内存分段默认分为8次回收,G1会优先回收垃圾多的内存分段,垃圾占内存分段比例越高的会优先被回收**。并且有**一个阈值决定内存分段是否被回收:-XX:G1MixedGCLiveThresholdPercent,默认为65%,代表垃圾占内存分段比例要达到65%来回被回收**,如果垃圾占比太低,意味着存活的对象多,复制算法就会花费更多的时间区复制存活的对象。
* 必要的情况下(对象分配速度远大于回收速度),Full GC仍然会触发(Full GC的成本较高,单线程,性能差,STW时间长)
* 堆内存太小、对象分配速度远大于回收速度等原因都可以导致G1在复制存活对象的时候没有空闲的内存分段可用,最终造成Full GC的触发。
- 前言
- Write once, run anywhere
- 概述
- JAVA虚拟机
- JVM整体结构
- JVM架构模型
- JVM虚拟机分类
- HotSpot VM
- JRockit
- IBM-J9
- Azul/zing VM
- Taobao VM
- Dalvik VM
- Graal VM
- JAVA源码编译机制
- Javac编译器
- 分析和输入到符号表
- 注解处理
- 语义分析和生成class文件
- ECJ编译器
- 类执行机制
- 字节码解释执行
- 栈顶缓存
- 部分栈帧共享
- 编译执行
- 即时编译器
- C1 Compiler
- C2 Compiler
- Graal编译器
- C1与C2编译器
- AOT
- 编译优化
- 字符串优化
- 方法内联
- 逃逸分析
- 同步消除
- 标量替换
- 栈上分配
- 去虚拟化/逆优化
- 多层编译
- JVM编译策略
- OSR编译
- 冗余削除
- CodeCache
- 常量编译优化
- JVM运行时数据区
- 程序计数器
- JAVA虚拟机栈
- 栈帧
- 局部变量表
- 操作数栈
- 本地方法栈
- Java调用native方法
- JVM Stacks && Native Stacks
- 堆-Heap
- 方法区(Method Area)
- 运行时常量池
- 常量传播优化
- MetaSpace
- 直接内存
- StackOverflowError
- 递归方法
- OutOfMemoryError
- 本地内存溢出
- 执行引擎
- 运行时数据区关联关系
- jdk8内存结构
- JMM内存模型
- JAVA内存模型
- JMM八种操作指令
- 内存屏障
- 指令重排
- as-if-serial语义
- Happen-Before规则
- 数据依赖性
- 原子性、可见性与有序性
- 伪共享
- CPU三级缓存
- 缓存行
- MESI协议
- Java中的伪共享
- ConcurrentHashMap伪共享解决方案
- 虚拟机对象
- 对象创建原理
- 对象内存布局
- 对象头
- 实例数据
- 对象的访问定位
- 垃圾收集器与内存分配策略
- GC相关概念
- TLAB
- JVM GC工作原理
- 内存管理
- JAVA引用分类
- 死亡标记
- 回收方法区
- 三色标记算法
- 垃圾收集算法
- 标记-清除算法
- 标记-整理算法
- 复制算法
- 分代收集算法
- HotSpot算法实现
- STW
- 垃圾收集器
- 常见的垃圾收集器
- 垃圾收集器分类
- Serial收集器
- Serial Old收集器
- ParNew收集器
- Parallel Scavenge收集器
- Parallel Old收集器
- CMS收集器
- CMS完整收集过程
- Card Table
- G1收集器
- 分代收集
- 空间整合
- 可预测的停顿时间模型
- G1&CMS
- 主要参数说明
- G1适用场景
- Remembered Set
- G1垃圾回收的过程
- G1优化建议
- Shenandoah
- ZGC
- 垃圾收集器特点
- GC日志
- GC策略的评价指标
- jvm card table数据结构
- 对象生存轨迹
- 类文件结构
- 魔数
- 版本号
- 常量池
- 访问标志
- 父类索引
- 接口集合
- 字段集合
- 方法集合
- 属性集合
- 类加载机制与类的初始化
- Java代码执行流程
- 类加载过程
- 抽象类ClassLoader
- 常见类加载器
- BootstrapClassLoader
- 自定义类加载器
- 线程上下文类加载器
- 双亲委派模型
- Tomcat类加载机制
- ServiceLoader
- 类的初始化
- 常见的JVM类加载异常
- ClassNotFoundException
- NoClassDefFoundError
- LinkageError
- ClassCastException
- 虚拟机性能调优监控与故障处理工具
- CPU利用率高/飙升
- 排查及解决方案
- 上下文切换
- GC问题定位解决方案
- prommotion failed
- FullGC频繁
- youngGC
- 内存问题
- 内存溢出和内存泄漏
- 内存溢出
- 栈溢出
- 堆溢出
- 对外内存溢出
- 内存泄漏
- 磁盘问题
- 线上问题解决方案
- 不定期出现的接口耗时现象
- 线程池异常
- 死锁问题
- JVM调优
- jvm参考配置
- jvm-jstat
- jvm-jmap
- jvm-jstack
- jinfo
- jps
- 虚拟机的退出
- Shutdown Hook
- JVM指令
- 附录
- 常用JVM指令
- Class文件版本号
- Class文件格式
- 方法访问标识
- jvm常量池
- 类或接口的访问标识
- 描述符标识字符含义
- 字段访问标识
- Java程序与Docker容器环境
- 基准测试