目录
垃圾收集器
一、 Serial (新生代)
1.单线程(收集期间需要暂停其他所有线程)
2.客户端模式下的默认收集器,也是很好的选择
优点:
1.简单高效(与其他收集器的单线程相比)
2.内存资源受限时,额外内存消耗最小
3.因为没有线程交互的开销,因此可获得最高的单线程效率
二、 ParNew(新生代)
是Serial的多线程版本。
多线程并行收集,其余与Serial相同
-
多线程并行收集(用户进程需等待)
-
目前唯一一个能与CMS收集器配合工作
使用
-XX:+UseConcMarkSweepGC
参数,使用ParNew+CMS+Serial Old收集器组合,此时ParNew是新生代的默认收集器 -
默认开启的收集线程数与处理器核心数相同。
可以使用
-XX:ParallelGCThreads
参数限制线程数
对垃圾收集时系统资源的高效利用很有好处。
JDK9后取消的组合:
- ParNew+CMS(未被取消,但不推荐作为服务端模式下的收集器组合)
- ParNew+Serial Old
- Serial+CMS
因此只有ParNew和CMS可以组合,也可以理解为ParNew并入了CMS
三、 Parallel Scanvenge(新生代)
1.基于标记-复制
2.并行收集
3.关注吞吐量
适合在后台运算而不需要太多交互的任务
相关参数
-
-XX:MaxGCPauseMillis
大于0的毫秒数
最大垃圾收集停顿时间(以牺牲年轻代大小为代价,每次停顿时间短,但导致GC更频繁,降低吞吐量)
-
-XX:GCTimeRatio
0-100 整数:垃圾收集时间占总时间的比率,这里原书概念有点乱不太好理解。
但只要只要参数值N是通过公式: 1/1+N 来计算垃圾收集时间占总时间的比率,而N本身不是该比率
如参数为49时:吞吐量=1-1/(1+49)=1-1/50 = 98% 1/50就是垃圾收集时间的占比
-
-XX:UseAdaptiveSizePolicy
开启该参数,不需要指定新生代大小(-Xmn)、-XX:SurvivorRatio、-XX:PretenureSizeThreshold等参数,只需要指定一个优化目标:如最大堆-Xmx,以及上面参数1或2,指定是关注停顿时间还是吞吐量,系统会动态的调整相关参数,此称为垃圾收集的自适应的调节策略
四、 Parallel Old(老年代)
Parallel Scavenge的老年代版本
- 多线程并发收集
- 基于标记整理
- 注重吞吐量(Parallel Scanvenge + Parallel Old)
五、 Serial Old(老年代)
Serial的老年代版本
1.单线程
2.标记整理
3.同Serial供客户端模式使用
4.作为CMS收集器失败时的后备预案
工作过程图参考一、 Serial收集器
六、 CMS(老年代)
关注停顿时间—> B/S等服务端模式中给用户给拱良好的交互体验
运作过程
1.初始标记 (Initial Mark)
2.并发标记(Concurrent Mark)
3.重新标记(Remark)
4.并发清除(Concurrent Sweep)
1.初始标记:标记与GCRoots直接关联的对象,速度快
2.并发标记:从GCRoots直接关联的对象开始遍历整个对象图,不暂停用户线程,可与GC线程并发运行,耗时长
3.重新标记:修正并发标记期间因用户线程导致标记产生变动的对象
4.并发清除:删除掉被标记死亡的对象,可与用户程序并发运行
缺点
-
对CPU资源敏感
CMS默认启动的回收线程数是**(cpu数量+3)/4**。所以CPU数量少会导致用户程序执行速度降低较多。
-
无法处理浮动垃圾
两个并发阶段因为用户程序仍要执行,因此会产生新的垃圾对象,而这些对象因出现在标记后,只能在下一次GC时回收,这些就是浮动垃圾。
-
CMS不能等到老年代对象满了才触发。
-
参数
-XX:CMSInitiatingOccupancyFraction
:设置CMS触发的阈值,在jdk1.6中CMS默认启动阈值提升到了92%。 -
-XX:+UseCMSInitiatingOccupancyOnly :默认false,如果不使用,则只会使用一次上面设置的阈值,后面会自动调整;如果使用,则始终使用设定的阈值。
可以将这两个参数搭配使用:
-XX:CMSInitiatingOccupancyFraction=70 -XX:UseCMSInitiatingOccupancyOnly=true
- CMS运行期间预留的内存无法满足程序的需要时,会出现”Concurrent Mode Failure“,然后会临时启用Serial Old收集器进行老年代的垃圾收集,这样停顿时间就很长了,所以参数值设置太高容易导致大量”Concurrent Mode Failure“。
需要注意的是:
intx CMSInitiatingOccupancyFraction = -1 {product}
该参数的值在jdk1.8中默认值为-1,而92%的计算方式如下:
void ConcurrentMarkSweepGeneration::init_initiating_occupancy(intx io, uintx tr) { assert(io <= 100 && tr <= 100, "Check the arguments"); if (io >= 0) { _initiating_occupancy = (double)io / 100.0; } else { _initiating_occupancy = ((100 - MinHeapFreeRatio) + (double)(tr * MinHeapFreeRatio) / 100.0) / 100.0; } }
-
如果参数值大于等于0,则就按该百分比作为参数
-
如果小于0,则根据
MinHeapFreeRatio
和tr
按上面公式计算,MinHeapFreeRatio
是堆大小达到这个该百分比时会缩小堆(-Xms=-Xmx时无效),tr即CMSTriggerRatio
,查看可知这两个参数的默认值为:uintx MinHeapFreeRatio = 40 {manageable} uintx CMSTriggerRatio = 80 {product}
因此得出的触发CMS的百分比为:((100-40)+(80*40)/100)/100=0.92=92%
-
-
存在空间碎片,因为基于标记-清除算法
- -XX:+UseCMSCompactAtFullCollection :FullGC时进行碎片整理,独占无法并发,会使停顿时间变长,JDK9开始废弃
- -XX:+CMSFullGCsBeforeCompaction 设置经过几次FullGC后进行碎片整理
总结
CMS触发阈值不能太大,否则会造成并发失效,也不能太小,会造成频繁触发CMS,而CMS的初始标记和重新标记(耗时长)会造成Stop The World,增大停顿时间,可设置为70
七、 Garbage First(G1)
1.面向服务端(JDK9开始作为服务器模式下的默认垃圾收集器,CMS则被Deprecate)
2.面向堆内存任何部分来组成回收集(CSet),衡量的标准变为哪块内存中存放的垃圾数量最多,回收收益最大
-
G1将堆分为大小相等的独立区域(Region),每个region都可以扮演eden、survivor和老年代,收集器能够针对不同角色的region采取不同的策略区处理,region是单次回收的最小单元,即每次回收的内存空间都是region的整数倍
-
Region中的Humongous(当做老年代看待)区域用于存储大对象,只要超过一个region容量一半的对象都可以称为大对象
参数
-XX:G1HeapRegionSize
参数可以指定每个region大小,范围:1M~32M
间的2的幂次数超过整个region的超大对象被放于连续的Homongous中
-
G1按照各个region垃圾的价值(即回收获得的空间与所需时间的经验值),维护一个优先级列表,依据用户设置的停顿时间优先回收价值大的region
几个问题
-
G1使用记忆集避免整堆扫描
-
并发标记阶段保证收集线程和用户线程互不干扰的方式:
G1通过原始快照方式保证用户线程改变引用关系时,原本的对象图结构不被破坏,保证标记的正确性。参考上文4.6(原始快照)
- G1为每个region设计了两个TAMS(Top At Mark Start)指针
- 划分一部分空间用于并发回收时产生的新对象的分配
- 新对象地址必须在两个指针位置以上
- G1默认在该地址以上的对象是存活的
- 当分配对象的速度超过回收速度时,冻结用户线程,产生Full GC
运行过程
-
初始标记
标记GCRoots直接关联的对象,修改TAMS指针值,让下个阶段用户进程并发运行时,能够在可用的region中分配新对象。同前面一样,需要STW,但耗时很短,且是借助MinorGC时同步完成,因此G1在该阶段不需要额外的停顿。
-
并发标记
从GCRoots直接关联的对象对对象图做可达性分析,找出回收对象,耗时长。
可与用户进程并发执行,扫描完成后,需重新处理SATB(原始快照)记录下的并发时引用变动的对象。
-
最终标记
对用户线程作另一个短暂暂停,用于处理并发阶段结束后遗留下的少量SATB记录。
-
筛选回收
- 更新region的统计数据
- 对所有region按照回收价值和成本排序
- 根据用户设定的停顿时间制定回收计划
- 自由选择多个region作为回收集,将决定回收的region的存活对象复制到另一个空的region,再清理整个旧region
- 需暂停用户进程,可多条收集器并行执行。
更新->排序->制定回收计划->选择回收集->复制清理(并行、STW)
除了并发标记,都需要停止用户进程。
示意图如下:
特色和优点:
-
延迟可控情况下获得尽可能高的吞吐量。
-
用户可指定期望(要符合实际)的停顿时间
默认为200ms,不能太低,太低会造成每次选择的回收集只占堆的很小一部分,回收速度跟不上分配速度,导致Full GC
-
分区(region)管理堆内存
-
按回收效益确定回收集
-
整体基于标记-整理,局部基于标记-复制,不会产生碎片
缺点(与CMS比)
-
内存占用
因为每个region都需要维护一个卡表解决跨代引用问题,记忆集可能占整堆的20%以上。CMS则只有一份卡表。
-
额外执行负载高
和CMS一样都使用写屏障维护卡表。
除了和CMS一样使用写后屏障外(直接使用同步),G1还需要写前屏障实现原始快照算法,产生额外负担。(使用消息队列)
小内存应用CMS表现大概率由于G1
======================================================================
其他相关笔记:
JVM笔记(一)java内存区域与内存溢出以及对象的创建、布局和定位
JVM笔记(二)对象的生死与java的四大引用
JVM笔记(三)垃圾收集算法以及HotSpot的算法实现(安全点、记忆集与卡表、写屏障、三色标记等)
JVM笔记(五)类加载机制、类加载器和双亲委派机制
================================================================
参考:
《深入理解java虚拟机第三版》