垃圾收集器
概述
垃圾:
在运行程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾。
垃圾回收期:
垃圾收集器就是负责在指定的情况下去回收这些垃圾,
评估回收器的性能指标:
吞吐量:
运行用户代码的时间占运行时间的比例
垃圾收集开销:
吞吐量的补数,垃圾收集器占总运行时间的比例
暂停时间:
执行垃圾收集时,程序的工作线程被暂停的时间
收集频率:
相对应用程序的执行,收集操作发生的频率
内存占用:
java堆区所占内存的大小
现在的标准:在最大吞吐量优先的情况下,降低停顿时间
几款常见的垃圾回收器
串行回收器:Serial,Serial Old
并行回收器:ParNew,Paraller Scavenge, Parallel Old
并发回收器:CMS ,G1
新生代收集器:Serial, ParNew , Parallel Scavenge
老年代收集器:Serial Old, Parallel Old ,CMS
整堆收集:G1
垃圾回收器之间的组合使用关系
Serial回收器:串行回收
Hotspot中Client模式下的默认新生代垃圾回收器 ,
Serial:采用复制算法,串行回收和 stop the word机制的方式回收
Serial Old:同样采用串行回收好热stop the word 机制 只不过内存回收算法使用的是标记-压缩算法
Serial Old是运行在Client模式下默认的老年代的垃圾回收器
Serial Old在server模式下主要有两个用途 于新生代Parallel Scavenge配合使用 作为老年代回收器CMS的后备垃圾回收方案
ParNew回收器:并行回收(-XX:+UserParNewGC) (-XX:ParallelGCThreads)限制线程数量,默认开启和CPU数据相同的线程数
如果说Serial GC是年轻代中的单线程垃圾收集器,那么ParNew收集器则是Serial收集器的多线程版本
ParNew 收集器除了采用并行回收的方式执行内存回收外,两款垃圾收集器之间几乎没有区别。ParNew收集器在年轻代中也是采用复制算法,stop the world机制
ParNew 是很多JVM运行在Server模式下新生代的默认垃圾收集器
在单个CPU的环境下,ParNew收集器不比Serial收集器更高效
Parallel Scavenge回收器:吞吐量优先
-XX:MaxGCPauseMillis 设置最大停顿时间 STW
-XX:GCtimeRatio:垃圾收集时间占时间总比 用于衡量吞吐量
-XX: UseAdaptiveSizePolicy 设置Parallel 收集器具有自适应调节功能
Parallel 收集器同样也采用了复制算法 并行回收 和stop the world机制
和ParNew 不同 Parallel 收集器的目标则是达到一个可控制的吞吐量,也被称为吞吐量优先的垃圾收集器
自适应调节也是Parllel 与ParNew一个重要区别
高吞吐量则可以高效地利用CPU时间,尽快完成程序的运算任务,主要适合后台运算而不需要太多交互的任务。因此,常用在服务器环境中使用。例如批量处理,订单处理,工资支付,科学计算的应用程序
在Parllel 收集器在JDK1.6时提供了用于执行老年代收集的Parallel Old收集器,用来代替老年代的Serial Old收集器
Parallel Old收集器采用了标记-压缩算法,但同样也是基于并行回收和stop the world机制
CMS回收器:低延迟
JDK1.5 Hotspot 推出一款强交互应用中几乎可认为具有划时代一样的垃圾收集器 Concurrent-Mark-Sweep 收集器,这款收集器是Hotspot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾线程与用户线程同时工作
CMS的垃圾收集算法采用标记-清除算法,并且也会stop the world
初始标记Initial-Mark 阶段:所有程序都会Stop the world 这个阶段主要任务 仅仅只是标记处GC Roots能直接关联到的对象 ,一旦标记成功就会恢复之前被暂停的所有线程,由于直接关联对象比较小,所有速度很快
并发标记ConCurrent-Mark阶段:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
重新标记Remark阶段:用于并发标记阶段中,程序工作线程会和垃圾收集线程同时运行或交叉运行,因此为了修正并发标记期间,因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常比初始标记稍长一些,但比并发标记短
并发清除Concurrent-Sweep阶段:清理删除掉标记阶段判断已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段可以和用户线程同时并发。
因为垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用,因此CMS收集器不能像其他收集器那样,等到老年代几乎完全被填满再进行收集,而是当堆内存使用率达到某一阈值时,便开始回收。当预留内存无法满足程序需求,就会出现一次Concurrent Mode Failure失败,这时虚拟机启动后备方案,临时启用Serial Old 收集器来重新进行老年代的垃圾收集 ,这样停顿时间就很长了
优点:
并发收集,低延迟
缺点:
1:会产生内存碎片,导致并发清除后,用户线程可用空间不足,在无法分配大对象的情况下,不得不提前出发Full GC
2:CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用一部分线程而导致应用程序变慢,总吞吐量很低
3:CMS收集器无法处理浮动垃圾。可能出现ConCurrent Mode Failure失败而导致另一个Full GC的产生。在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运作或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法对象这些垃圾对象进行标记,最终导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间。
小结
最小化内存和并行开销 Serial GC
最大化应用程序的吞吐量 Parallel GC
最小化GC中断和停顿时间 CMS GC
G1回收器:区域化分代式
官方给G1设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所有才担当起全功能收集器的重任与期望。
G1是一个并行回收器它把堆内存分割成很多不相关的区域(Region)物理上不连续。使用不同的Region 来表示Eden 幸存者0区,幸存者1区,老年代等
G1 GC有计划地避免整个java堆进行全区域的垃圾回收,G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region
这种方式侧重点在回收最大量的区间 Region 所以我们给G1起一个名字 垃圾优先Garbage First
G1回收器的特点(优势)
并行与并发
并行性:G1在回收期间可以有多个GC线程同时工作,有效利用多核计算能力。此时用户线程STW。
并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个垃圾回收阶段发生阻塞应用程序的情况
分代收集
从分代上看,G1依然属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代依然有Eden区和Survivor区。但从堆的结构来看,它不要求整个Eden取,年轻代,或者老年代是连续的,也不再坚持固定大小和固定数量
将堆空间分为若干个区域(Region),这些区域中包含了逻辑上的年轻代和老年代。
和之前的各类回收器不同,它同时兼顾年轻代和老年代。
空间整合
CMS:标记-清楚算法,内存碎片,若干次GC后进行一次碎片整理
G1将内存划分成为一个个的region。内存的回收是以region作为基本单位的。region之间是复制算法,但是整体上实际可看做标记压缩算法。两种都是避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前出发下一次GC,尤其是当JAVA堆非常大的时候,G1的优势更加明显
可预测的停顿时间模型
这是G1相对于CMS的另一大优势,G1除了追求低停顿外,还建立可预测的时间模型,让使用者明确指出一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过n毫秒
由于分区的原因,G1可以值选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制
G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值)在后台维护一个优先列表,每次根据允许的收集时间 优先回收价值最大的Region 保证了G1收集器在有限的时间内可以会的尽可能高的收集效率
相比CMS GC,G1未必能做到CMS在最好的情况下的延时停顿,但是最差情况要好得多
缺点
相对应CMS G1还不具备全方位,压倒性优势。比如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用,还是程序运行时的额外执行负载,都比CMS要高
从经验上来说,在小内存应用上CMS的表现大概率优于G1,而G1在大内存应用上则发挥其优势,平衡点在6-8G之间。
-XX:+UseG1GC 设置G1收集器完成内存回收任务
-XX:设置每个Region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小java堆大小划分出约2048个区域。默认是堆内存的1/2000
-XX:MaxGCPauseMillis:设置期望达到的最大GC停顿时间。(JVM会尽力实现,但不保证达到)。默认值是200ms。
-XX:ParallelGCThread:设置STW工作线程数的值。最多设置为8
-XX:ConGCThreads:设置并发标记的线程数。将n设置为并发垃圾回收线程数1/4左右。
-XX:InitiatingHeapOccupancyPercent:设置触发并发GC周期的java堆占用率阈值。超过阈值发GC。默认值是45
G1回收器使用场景
面向服务器应用,针对具有大内存,多处理器的机器(普通大小的堆里表现并不惊艳)
以下场景G1优于CMS收集器
1:超过百分之五十的java堆被活动数据占用
2:对象分配频率或年代提升频率变化很大
3:GC停顿时间过长(长于0.5至1秒)
当JVM的GC线程处理速度慢时,系统会调用应用程序帮助加速垃圾回收过程
设置H的原因
对于堆中的大对象,默认直接会被分配到老年代,但是如果它是一个短期存在的大对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分一个Humongous区,它用来专门存放大对象。如果一个H区装不下,那么会寻找连续的H区来存储,为了能找到连续的H区,有时候不得不启动Full GC。G1大多数行为都把H区作为老年代来看待。
G1回收器垃圾回收过程
G1 GC回收过程主要包括以下三个环节
年轻代GC (Young GC)
老年代并发标记过程 (Concurrent Marking)
混合回收(Mixrd GC)
当年轻代的Eden区用尽时开始年轻代回收过程;G1的年轻代收集线程是一个并发的独占式收集器,年轻代回收器,G1 GC暂停所有用户线程,启动多线程执行年轻代的回收,然后从年轻代移动存活对象到Survivor区间或者老年区间。
当堆内存达到一定值(默认45%)开始老年代并发标记过程
标记完成后马上开始混合回收。对于一个混合回收期,G1 GC从老年区间启动存活对象到空闲空间,这些空闲空间也就成了老年代的一部分,和年轻代不同,老年代的G1回收器和其他GC不同,G1 的老年代回收器不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代的Region就可以了。同时这个老年代Region是和年轻代一起被回收的。
Remenbered Set
一个对象被不同区域引用的问题
一个Region不可能是孤立的,一个Region中的对象可能被其它任意Region中的对象引用。
判断对象是否存活时,是否需要扫描整个java堆才能保证准确。
在其它分代回收器也存在这样的问题,(而G1更突出)
回收新生代也不得不同时回收老年代?
这样的话会降低Minor GC的效率。
解决方法
无论是G1还是其它分代收集器,JVM都是使用Remenberd Set来避免全局扫描。
每个Region都有一个对应的Remenbered Set
每次检查要写入的引用指向的对象是否和该Reference 类型数据在不同的Region(其它收集器:检查老年代对象是否引用了新生代对象)
如果不同通过CardTable把相关引用信息记录到引用指向对象所在Region对象的Remenbered Set中
当进行垃圾收集时,有GC根节点的枚举范围加入Remenbered Set;就保证不进行全局扫描,也不会有遗漏。
G1回收过程:年轻代GC
第一阶段,扫描根
根是指static变量指向的对象,正在执行的方法调用链条上的局部变量等。根引用连同Rset记录的外部引用作为扫描存活对象的入口
第二阶段:更新Rset
处理dirty card queue中的card,更新Rset,此阶段完成后,Rset
可以准确的反应老年代对内存分段中对象的引用
处理object field = object,jvm会在之前和之后执行特殊操作在dirty card queue中入队一个保存对象引用信息的card,在年轻代回收的时候,会对Dirty Card queue中的所有 card进行处理,以更新Rset,保证Rset可以反应准确的映射关系。 如果赋值的时候直接操作Rset,Rset的处理需要线程同步,开销很大,使用队列性能会好很多。
第三阶段:处理Rset
识别被老年代指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活对象
第四阶段:复制对象
此阶段,对象树被遍历,Eden区内存存活的对象都会被复制到Survivor区中空的内存分段,
Survivor区中年龄到达阈值的对象会被分配到Old区,年龄未达到的年龄+1,如果Survivor区空间不够,Eden空间的部分数据会直接晋升到老年代空间。
第五阶段:处理引用
处理Soft Weak Phantom Final JNI 等没用引用,最终Eden空间数据为空,GC停止工作。而目标内存中的对象都是连续存储的,没有碎片,所有复制过程可以达到内存整理的效果整理碎片。
G1回收过程2:并发标记过程
初始标记阶段:
标记从根节点直接可达的对象,这个阶段是STW的,并且会触发一次年轻代GC。
根区域扫描:
G1 GC扫描Surivor区可达的老年代区域对象,并标记被引用的对象,这一过程必须在yong GC之前完成。
并发标记:
在整个堆里并发标记,此过程可能被yongGC中断,在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。同时,并发表姐过程中,会计算每个区域对象存活的比例
再次标记
由于应用程序持续执行,需要修正上一次的标记结果,是STW的,G1中采用了比CMS更快的初始快照算法:snapshot—at—the—beginning(SATB)。
独占清理
计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫。是STW的 这个阶段并不会实际做垃圾的收集
并发清理
识别并清理完全空闲的区域
G1回收阶段3:混合回收
当越来越多的对象晋升到老年代oldregion时,为了避免堆内存被耗尽,虚拟机会触发一个混合收集器mixed GC 该算法并不是一个Old GC 除了回收整个Young Region,还会回收一些Old Region。是一部分老年代,而不是老年代。
G1回收阶段:Full GC
G1的初衷是避免Full GC 当时上述方式不能正常工作,G1会停止用户线程,使用单线程的内存回收算法进行垃圾回收,性能很差,而且用户停顿时间会很长 。
总结
秃头小白一只 总结的一些资料 希望可以帮助到各位