写在前面,关于JVM有太多文章可以参考,
1,类加载机制、双亲委派模型
2,运行时数据区(元空间(持久代)、堆、栈、本地方法栈、程序计数器)
3,垃圾回收策略
4,JVM相关问题
不一一详细写了,先写点网络上讲的不清楚的内容吧,算是整理,并非创造
CMS(Concurrent Mark Sweep)收集器
1、特点
针对老年代;
基于"标记-清除"算法(不进行压缩操作,产生内存碎片);
以获取最短回收停顿时间为目标;
并发收集、低停顿;
需要更多的内存(看后面的缺点);
是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器;
第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;
2、应用场景
与用户交互较多的场景;
希望系统停顿时间最短,注重服务的响应速度;
以给用户带来较好的体验;
如常见WEB、B/S系统的服务器上的应用;
3、设置参数
"-XX:+UseConcMarkSweepGC":指定使用CMS收集器;
4、CMS收集器运作过程
主要有四个阶段,即:初始标记、并发标记、重新标记、并发清理
4.1、初始标记
仅标记一下新生代和老年代中GC Roots能直接关联到的对象; 速度很快; 但需要"Stop The World";通过-XX:+CMSParallelInitialMarkEnabled参数可以开启该阶段的并行标记,使用多个线程进行标记,减少暂停时间。
哪些对象可以作为GCRoot:
a、 虚拟机栈(栈帧中的本地变量表)中引用的对象。
b、 本地方法栈中JNI(即一般说的native方法)引用的对象。
c、 方法区中的静态变量和常量引用的对象。
4.2、并发标记
进行GC Roots Tracing的过程;
并发标记就需要标记出 GC roots 关联到的对象 的引用对象有哪些。比如说 A -> B (A 引用 B,假设 A 是 GC Roots 关联到的对象),那么这个阶段就是标记出 B 对象, A 对象会在初始标记中标记出来。
三色标记法
这个算法就是把 GC 中的对象划分成三种情况:
白色:还没有搜索过的对象(白色对象会被当成垃圾对象)
灰色:正在搜索的对象
黑色:搜索完成的对象(不会当成垃圾对象,不会被GC)
问题点:假设有 A -> B -> C, A 是 GC Roots 关联的对象,那么首先会把 GC Roots 标记,也就是 A 标记成灰色(证明现在正在搜索 A 相关的),然后搜索 A 的引用,也就是 B,那么搜索了 B,把 B 变成了灰色,那么 A 就搜索完成了。(此时注意,现在是不管 C 的,因为 C 不是 A 的引用,现在只管 A 的引用是什么)。此时把 A 相关的搜索完了,那么 A 就变成了黑色,证明 A 已经 ok 了。
(Ps:浮动垃圾就是说,此时 A 又不用了,那么 A 是没办法回收的,因为 A 已经标记了)
此时准备要搜索 B 了。
刚好,此时,用户线程要执行了,用户线程把原来 A -> B -> C 的引用改成了 A -> C,同时 B 不再引用C。
然后又到 GC 线程执行了。
GC 线程发现 B 没有引用的对象了(因为用户线程已经把 B -> C 去掉了),那么 B 就相当于搜索完成了,变成黑色了。
最后,C 怎么办,C还是白色的呢,白色的是不会搜索,当做垃圾处理的。
解决办法
此时的解决办法就是有一个叫做写入屏障的东西。就是说,如果A已经被标记了(已经是黑色的了),那么用户线程改动 A->C的时候,会把 C 变成灰色,这样,以后就可以搜索 C了。
GC 线程和 用户线程并发的时候,用户线程把失效的对象又置为有效,这个时候怎么处理。
就是用重新标记
4.3、重新标记
为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录;
需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
采用多线程并行执行来提升效率;
重新标记是干什么的呢?就是由于并发标记这个阶段用户线程和GC 线程并发,假如这个阶段用户线程产生了新的对象,这个对象是白色的,总不能被 GC 掉吧。这个阶段就是为了让这些对象重新标记。
4.4、并发清除(CMS concurrent sweep)
回收所有的垃圾对象;
整个过程中耗时最长的并发标记和并发清除都可以与用户线程一起工作;
所以总体上说,CMS收集器的内存回收过程与用户线程一起并发执行;
5、CMS收集器3个明显的缺点
- 缺点:
- 1、对CPU资源敏感
- 问题:并发阶段虽然用户线程不停顿,但会占用CPU资源导致用户线程变慢,吞吐量降低。
- 默认回收线程数:(CPU数量+3)/4。
- 当CPU>4时,并发线程>25%的CPU资源。且随CPU数量增加而下降。
- 当CPU<4时(假设为2),并发线程>50%的CPU资源,很影响用户体验。
- 默认回收线程数:(CPU数量+3)/4。
- 解决:
- (不提倡使用)提供“增量式并发收集器”:并发标记和并发清除阶段让GC线程和用户线程交替运行,减少GC线程的独占资源时间。会增长GC时间,但降低用户影响。
- 问题:并发阶段虽然用户线程不停顿,但会占用CPU资源导致用户线程变慢,吞吐量降低。
- 2、无法处理浮动垃圾:
- 1、浮动垃圾:进行并发清除时用户线程运行产生的垃圾。只能在下一次GC时再清理。
- 2、如果获取对象实例的频率高于收集器清除堆里死对象的频率,并发算法将再次失败。从某种程度上说,老年代将没有足够的可用空间来容纳一个从年轻代提升过来的对象。这种情况被称为 “并发模式失败”,并且 JVM 会执行堆碎片整理:触发 Full GC。
并发清理阶段用户线程运行需要预留空间,老年代没有填充满就会进行GC。- JDK1.5:该GC启动百分比阈值为68%
- JDK1.6:该GC启动百分比阈值为92%
- 可通过:-XX:CMSInitiatingOccupancyFraction配置(太高会引发大量问题3)。
- 3、老年代GC如果预留空间不足,会出现“Concurrent Mode Failure”,此时虚拟机会启动后被预案,临时启用Serail Old收集器,而导致另一次Full GC的产生; 这样的代价是很大的,所以CMSInitiatingOccupancyFraction不能设置得太大。
- 3、基于标记-清除算法:
- 问题:会产生空间碎片。大对象分配会因无法找到连续内存空间而触发FGC
- 解决:
- +UseCMSCompactAtFullCollection参数:在CMS要进行FGC时开启内存碎片的合并整理过程。默认开启。
- 引发问题:内存整理导致停顿时间变长
- -XX:CMSFullGCsBeforeCompaction参数:设置N次不压缩的FGC后跟着来一次带压缩的FGC。默认为0,即每次FGC都进行碎片整理。
- +UseCMSCompactAtFullCollection参数:在CMS要进行FGC时开启内存碎片的合并整理过程。默认开启。
- 1、对CPU资源敏感
G1收集器