10分钟弄懂JVM中的CMS垃圾收集器
Posted Java架构设计
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了10分钟弄懂JVM中的CMS垃圾收集器相关的知识,希望对你有一定的参考价值。
CMS是基于标记-清除算法的,收集的时候分为4个步骤:
-
初始标记
-
并发标记
-
重新标记
-
并发清除
初始标记
初始标记仅仅只是标记一下GC Roots能直接关联到的对象,所以速度很快。比如下图,这边的GC Roots只用了虚拟机栈为例。两个虚拟机栈分表创建了对象OBJ_A1和OBJ_B1,他们也各有自己的其他引用,在这个阶段,他只会标记OBJ_A1和OBJ_B1,其他的引用是不标记的,所以尽管这个阶段有STW,但是标记的数量少,时间很快,基本不影响。
并发标记
并发标记就是根据初始标记的对象所直接或间接引用的对象进行标记,比如下图对OBJ_A2,OBJ_AN进行并发标记。这个阶段并没有STW,所以可以创建对象,新增新的引用,也会让某些对象失去引用,比如下图,OBJ_B1已经变成垃圾了,OBJ_C1是新增存活的对象。这个阶段由于对老年代所有的对象进行跟踪,所以是非常耗时的。
重新标记
在并发标记中,我们看到存活对象OBJ_C1等以及垃圾对象OBJ_B1等是没有被标记出来的,所以这个阶段就是对这些对象进行重新标记。这个阶段也有STW,但是仅仅对并发标记中有变动的对象进行标记,这些数量比较少,所以速度也是很快。
并发清除
这个阶段,就是在重新标记后,对垃圾对象的清理,和并发标记一样,都很耗时,由于并没有STW,所以对程序的运行影响不大。CMS采用的是标记与清除算法。
缺点
CMS的4个阶段,初始标记和重新标记需要STW,但是时间短,影响不大。并发标记和并发清除不需要STW,虽然耗时,但是并发执行的,影响也不大,看起来CMS很完美,但是他也有一些缺点。
CPU
CMS默认启动的回收线程数是(CPU数量+3)/ 4,也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。比如服务器是2核4G,那就需要用(2+3)/4=1个线程去处理并发的标记和并发清除,这时候只剩下1个线程处理其他事情。
浮动垃圾
浮动垃圾的产生,主要是在并发清理阶段。重新标记后,CMS垃圾回收器会知道哪些需要清理,在并发清理阶段,清理重新标记后的垃圾对象,这个阶段并没有STW,所以有可能产生新的对象。
比如下图的OBJ_N,创建完后,栈帧被回收,引用就没了,他在这个阶段是不能被清除的,只能等下一次垃圾回收的时候,被标记并清除。
如果这个阶段进入老年代的对象超过了剩余空间,就会出现Concurrent Mode Failure失败,那虚拟机会临时启用Serial Old收集器进行老年代的垃圾收集。
可以用XX:CMSInitiatingOccupancyFraction设置老年代空间被占用多少百分比触发CMS回收,JDK1.6后默认92%。
空间碎片
在《JVM垃圾回收算法》一文中提过,标记-清除算法会产生空间碎片的,如果连续的内存空间不够存放即将进入老年代的对象,此时就会触发Full GC。
为了避免这种情况,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection,默认打开的,当Full GC完成后,他会STW,进行内存整理,把存活的对象紧密的靠在一起,腾出连续空间。
如果每次都要重新内存,那都会STW,所以CMS还提供了-XX:CMSFullGCsBeforeCompaction参数,默认是0,表示进行了多少次Full GC后才整理内存。
JVM精彩问答- CMS垃圾回收器 | WeakHashMap工作原理 | Java语法糖
01
CMS垃圾回收器的工作过程,CMS收集器和G1收集器的区别?
CMS(Concurrent Mark Sweep) 收集器:是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:「初始标记,并发标记,重新标记,并发清除」,收集结束会产生大量空间碎片。如图:
「CMS收集器和G1收集器的区别」:
- CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
- G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
- CMS收集器以最小的停顿时间为目标的收集器;
- G1收集器可预测垃圾回收的停顿时间;
- CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片;
- G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
02
WeakHashMap了解过吗?它是怎么工作的?
「WeakHashMap」 类似HashMap ,不同点在WeakHashMap的key是「弱引用」的key。
谈到「弱引用」,在这里回顾下四种引用:
- 强引用:Object obj=new Object()这种,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
- 软引用: 一般情况不会回收,如果内存不够要溢出时才会进行回收。
- 弱引用:当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
- 虚引用:为一个对象设置虚引用的唯一目的只是为了能在这个对象被回收时收到一个系统的通知。
正是因为WeakHashMap使用的是弱引用,「它的对象可能随时被回收」。WeakHashMap 类的行为部分「取决于垃圾回收器的动作」,调用两次size()方法返回不同值,调用两次isEmpty(),一次返回true,一次返回false都是「可能的」。
WeakHashMap「工作原理」回答这两点:
- WeakHashMap具有弱引用的特点:随时被回收对象。
- 发生GC时,WeakHashMap是如何将Entry移除的呢?
第一点:WeakHashMap内部的Entry继承了WeakReference,即弱引用,所以就具有了弱引用的特点,「随时可能被回收」。看下源码:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
/**
* Creates new entry.
*/
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
......
第二点:「WeakHashMap是如何将Entry移除的?」 GC每次清理掉一个对象之后,引用对象会放到ReferenceQueue的,接着呢遍历queue进行删除。WeakHashMap的增删改查操作,就是直接/间接调用expungeStaleEntries()方法,达到及时清除过期entry的目的。可以看下expungeStaleEntries源码:
/**
* Expunges stale entries from the table.
*/
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
while (p != null) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
03
如是否了解Java语法糖嘛?说下12种Java中常用的语法糖
语法糖(Syntactic Sugar),也称糖衣语法,让程序更加简洁,有更高的可读性。Java 中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等12种。
语法糖一、switch 支持 String 与枚举;
语法糖二、 泛型;
语法糖三、 自动装箱与拆箱;
语法糖四 、 方法变长参数;
语法糖五 、 枚举;
语法糖六 、 内部类;
语法糖七 、条件编译;
语法糖八 、 断言;
语法糖九 、 数值字面量;
语法糖十 、 for-each;
语法糖十一 、 try-with-resource;
语法糖十二、Lambda表达式。
详解见:
END
来源 |
扫描二维码
获取更多精彩
海兴破颈记
以上是关于10分钟弄懂JVM中的CMS垃圾收集器的主要内容,如果未能解决你的问题,请参考以下文章