目录
1 垃圾收集算法
2 垃圾收集器
2.1 Serial
-XX:+UseSerialGC -XX:+UseSerialOldGC
串行收集器(单线程),不仅指一个垃圾收集线程回收垃圾,在其回收垃圾时也需要STW
新生代采用复制算法,老年代采用标记-整理算法
Serial Old收集器是Serial收集器的老年代版本,另一种用途是作为CMS收集器的后备方案
2.2 Parallel
JDK8默认的新生代和老年代收集器
-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)
是Serial收集器的多线程版本,默认的收集线程数跟cpu核数相同
关注点是吞吐量(高效率的利用CPU);吞吐量 = T(运行代码) / T(CPU总耗时间)
CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)
新生代采用复制算法,老年代采用标记-整理算法
2.3 ParNew
-XX:+UseParNewGC
Parallel收集器很类似
新生代采用复制算法
2.4 CMS
-XX:+UseConcMarkSweepGC(old)
获取最短回收停顿时间为目标的收集器;常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作
“标记-清除”算法
初始标记: 暂停所有的其他线程(STW) ,并记录下gc roots直接能引用的对象,速度很快
并发标记: 并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,不需要停顿用户线程
重新标记:主要是处理漏标问题;主要用到三色标记里的增量更新算法
并发清理:开启用户线程,同时GC线程开始对未标记的区域做清扫
并发重置:清除标记
主要优点:并发收集、低停顿
明显的缺点:
1 会和服务抢资源
2 无法处理浮动垃圾(并发标记和并发清理阶段又产生垃圾)
3 "标记-清除"算法,大量空间碎片
4 会存在上一次垃圾回收还没执行完;特别是在并发标记和并发清理阶段会出现;也许没回收完就再次触发full gc,此时会STW,切换成SerialOld收集器
CMS的相关核心参数
1. -XX:+UseConcMarkSweepGC:启用cms
2. -XX:ConcGCThreads:并发的GC线程数
3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次
5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,
JVM仅在第一次使用设定值,后续则会自动调整
7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,降低CMS GC标记阶段(也会对年轻代一起做标记,如果在
minor gc就干掉了很多对垃圾对象,标记阶段就会减少一些标记时间)时的开销,一般CMS的GC耗时 80%都在标记阶段
8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;
3 调优建议
原则:让短期存活的对象尽量都留在survivor区,不让其进入老年代;这样在minor GC时,对象就会被回收,不会进入老年代从而导致full GC
对象的分代年龄设置为多大较合理:(-XX:MaxTenuringThreshold=5)譬如一次minor GC可能间隔20-30s,若分代年龄设置为5就将对象放在老年代,整个过程大概也经历了2-3min,而这么长时间都没被回收的对象,完全可以认为这些对象是会存活比较长的对象,不让其继续占用survivor空间
多大的对象才是大对象:很少有对象能超过1M(-XX:PretenureSizeThreshold=1M)
参数设置参考如下:
-Xms3072M 初始堆内存大小
-Xmx3072M 最大堆内存大小
-Xmn2048M 新生代堆内存大小
-Xss1M 每个线程的栈大小
-XX:Metaspacesize=256M 元空间的初始大小
-XX: SurvivorRatio=8 用于设置新生代中Eden区与Survivor区的大小比例
-XX: MaxTenuringThreshold=5 设置对象进入老年代的最大年龄阈值
-XX:PretenureSizeThreshold=1M 设置对象在分配时是否直接分配到老年代
JDK8默认垃圾收集器是:-XX:+UseParallelGC(年轻代)和-XX:+UseParallelOldGC(老年代).;若内存(超过4G),系统对停顿时间比较敏感,可以使用ParNew+CMS(-XX:+UseParNewGC-Xx:+UseConcMarkSweepGC)
4 CMS垃圾收集器
4.1 三色标记算法
假设我们有一个对象图,其中包含多个对象,它们之间通过引用相互连接。我们的目标是标记哪些对象是可达的(活动的),哪些对象是不可达的(垃圾的)
初始时,所有对象都被标记为白色,表示它们都是未经检查的。然后,我们从根对象开始遍历对象图,标记根对象为灰色,表示它需要进一步检查。接着,我们执行以下步骤:
1. 从灰色对象中选择一个,标记为黑色,表示它已经被检查过
2. 检查当前黑色对象引用的所有对象
- 如果引用的对象是白色,将其标记为灰色,并将其添加到待处理对象列表中。
- 如果引用的对象已经是灰色或黑色,不进行任何操作
3. 重复步骤1和步骤2,直到没有灰色对象为止
这个过程不断迭代,直到所有可达对象都被标记为黑色。最终,剩下的白色对象即为不可达的垃圾对象,可以被回收
以下是一个示例:
假设有以下对象图:
```
A -> B -> C
| |
D E
```
- 初始状态:所有对象(A、B、C、D、E)都标记为白色
- 第一步:从根对象A开始,将A标记为灰色。
- 第二步:检查A引用的对象B和D,将它们标记为灰色,加入待处理列表
此时的状态:
```
A (灰色) -> B (灰色) -> C (白色)
| | |
D (灰色) E (白色)
```
- 第三步:选择一个灰色对象,例如B,将其标记为黑色,并检查B引用的对象C
- 第四步:将C标记为灰色,加入待处理列表。
此时的状态:
```
A (灰色) -> B (黑色) -> C (灰色)
| | |
D (灰色) E (白色)
```
- 第五步:选择一个灰色对象,例如D,将其标记为黑色。D没有引用其他对象,所以不需要进行进一步检查
此时的状态:
```
A (灰色) -> B (黑色) -> C (灰色)
| | |
D (黑色) E (白色)
```
- 第六步:选择一个灰色对象,例如C,将其标记为黑色。C没有引用其他对象,所以不需要进行进一步检查
此时的状态:
```
A (灰色) -> B (黑色) -> C (黑色)
| | |
D (黑色) E (白色)
```
- 第七步:选择一个灰色对象,例如E,将其标记为黑色。E没有引用其他对象,所以不需要进行进一步检查
此时的状态:
```
A (灰色) -> B (黑色) -> C (黑色)
| | |
D (黑色) E (黑色)
```
现在,所有可达对象都被标记为黑色,不可达的白色对象即为垃圾对象。这些垃圾对象可以被垃圾回收器回收
4.2 内存屏障
以下是CMS垃圾收集器中常用的内存屏障:
1. **写屏障(Write Barrier):** 写屏障用于记录对象引用的变化,以便在并发标记阶段识别新对象的引用关系。当应用程序修改对象的引用时,写屏障会将这个变化记录下来,以确保新对象的引用被及时标记。
2. **读屏障(Read Barrier):** 读屏障用于确保在读取对象引用之前,已经完成了并发标记阶段的标记工作。它可以防止应用程序在读取对象引用时看到未标记的对象,确保对象的可见性。
3. **内存屏障指令(Memory Barrier Instructions):** 内存屏障指令用于控制指令的执行顺序,确保内存操作的顺序性和一致性。它可以用于强制某些指令在内存屏障之前或之后执行,以确保数据的正确同步。
这些内存屏障的使用使得CMS垃圾收集器能够在并发执行的同时,确保垃圾回收的正确性和可靠性。例如,在并发标记阶段,当一个线程修改对象引用时,写屏障会记录这个变化,并在标记阶段检查这个引用关系,以确保对象被正确标记。
需要注意的是,CMS垃圾收集器的内存屏障会引入一些额外的开销,因为它需要记录和检查对象引用的变化。但这个开销通常是为了减少停顿时间而进行的权衡,以提高应用程序的响应性。
总之,CMS垃圾收集器使用内存屏障来确保多线程环境下的数据一致性和可见性,以实现并发的垃圾回收,从而减少应用程序的停顿时间。