1.垃圾收集算法
标记-复制算法、标记-清理算法、标记-整理算法,这三种算法都是在分代收集的机制上成立的
标记-复制算法:
其原理是将内存分成两部分,一部分内存1放对象,一部分内存2为空,当放对象的内存1进行垃圾回收时,会将仍然存活的对象标记起来,然后复制到另一块内存2中,再把内存1清空。然后进行第二次回收也是如此。这种方式经常在年轻代的垃圾回收中使用,即每次将被标记的仍然存活的对象放到另一块的survivor区中。此算法的缺点是:有一部分内存必须为空,才能存放那些仍然存活的对象,如果在老年代中使用,很浪费内存。
标记-清理算法
其原理是将内存中仍然存活的对象标记起来,然后清理掉没有被标记的对象(少数情况下会标记那些需要清理的对象,然后去清除掉)。此算法的优点是较上一种节省空间,缺点是,收集后的内存对象排列不整齐,会出现大量的不连续的垃圾碎片,二是,标记的对象过多,会很浪费时间。
标记-整理算法
标记-整理算法即标记到那些仍然存活的对象,将这些对象都朝着一个区域移动,最后所有的对象都是在一块连接的区域内,最后清理掉这个区域外的所有对象。清理后的内存都是连续的,并且不需要重新分配一块空白内存。
2.几种常见的垃圾收集器
serial垃圾收集器
serial(串行)垃圾收集器,是单线程收集器,在执行垃圾收集时,会STW(stop the world),停止一切其他的线程,是最早的垃圾收集器,用户体验不是很好,但是减少了与其他线程的来回切换,所以很简单高效(与其他收集器的单线程相比)。
serial old收集器是serial的老年代版收集器,其作用是在jdk1.5及之前与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案,当cms收集器出现“concurrent mode failure"时,会STW,并切换为该收集器进行处理。
serial收集器是在新生代中使用,使用的是标记复制算法
serial old收集器在老年代使用,适用的是标记整理算法
Parallel Scavenge收集器
parallel Scavenge收集器是serial收集器的多线程版本,默认使用和cpu核数相同的线程数去进行垃圾回收,在jvm参数中加入
-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)分别使用相应的收集器。
新生代采用标记复制算法,老年代使用标记整理算法
Parallel Old收集器是Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。JDK8默认的新生代和老年代收集器是:Parallel Scavenge收集器和Parallel Old收集器()。
parNew 收集器
与parallel收集器相似,不同点是与cms搭配使用的收集器。
新生代采用标记复制算法,老年代使用标记整理算法
cms收集器与三色标记算法
CMS(Concurrent Mark Sweep),并发标记清理。使用标记-清理算法,也就是说,垃圾回收后会产生很多碎片空间,可以添加(-XX:+UseCMSCompactAtFullCollection)参数,让jvm在垃圾收集后对空间进行清理,cms算法有着很强的并发效率,极大的提高了用户体验,大多数时间下都是在并发条件下执行的,所以是基本上是没有停顿的垃圾回收器。其收集回收阶段如下:
初始标记:启动cms垃圾收集器后,先进行初始标记,即,标记那些直接使用的对象,作为root对象。此阶段会执行STW,但是时间极短。
并发标记:使用那些root对象,使用可达性算法,对其引用的对象进行标记,此阶段为并发,不影响程序运行。
重新标记:在上述执行并发标记过程中会产生新的对象,使用三色算法,重新对新的对象进行标记,此阶段会执行STW,时间会比初始标记长,但实际时间很短。
并发清理:清理那些未标记的对象,此阶段为并发,不影响程序运行。
并发重置:垃圾对象清理后,将所有对象的标记状态都只为空,即三色标记里的白色。此阶段为并发,不影响程序运行。
Cms垃圾收集器底层使用三色收集算法:
黑色:该对象中及其所有引用的对象都已经被扫描,即该对象不会被再次扫描了
灰色:该对象已被扫描,但其引用的对象没有被扫描完成。只扫描了一部分
白色:该对象未被扫描,即所有对象的初始状态。
黑色对象无法直接引用白色对象。只能通过灰色对象间接引用。
详解:cms在初始标记阶段,将一部分对象从白色标记为灰色,在并发标记阶段,对这些灰色对象进行gc可达性算法分析,对其引用对象进行标记,当其所有的引用对象扫描完成后,该对象变成黑色,不会再次进行扫描。如果有的对象没有把引用扫描完,则为灰色,如果对象未被扫描,仍未白色。重新标记阶段,重新解决那些在并发标记过程中产生的对象,执行上述操作(此过程会产生问题)。并发清理阶段,清理那些标记为白色的对象。并发重置阶段,将剩余的对象都重置为白色。
问题:
如果在并发标记过程中,黑色的对象又添加了指向白色对象的引用(通过灰色对象进行添加引用,黑色对象无法直接指向白色,因为黑色的是所有的引用都被扫描的,引用没有白色的,都是灰色和黑色的),此时其中间引用灰色对象又去除了对该白色对象的引用(比如将引用置为空),所以,该灰色对象不会再扫描这个白色对象,黑色对象不会被再次扫描,所以其引用的这个白色对象在重新标记阶段不会被扫描的到了,在并发清理阶段进行时,仍未白色,所以会被清理。就会产生bug。
解决:
上述问题即为漏标的现象:解决方法有:原始快照与增量更新。
原始快照:当一个灰色对象对白色对象的引用删除时,将该引用添加一个快照到一块内存区域中,在重新标记阶段,重新进行扫描该区域中所有的引用中的灰色对象,就会扫描到其引用的这个白色对象。
增量更新:当黑色对象通过灰色对象间接引用白色对象时,将该黑色对象置为灰色,即在重新标记阶段,重新进行扫描,就会扫描到这个白色对象。
G1用原始快照,CMS用增量更新
在并发清理和并发重置阶段也会产生新的对象,会直接置为黑色,等待下次处理。
在这次垃圾回收过程中,并发过程会接着产生对象,也许没回收完就再次触发full gc。也就是"concurrent mode failure",此时会进入STW,用serial old垃圾收集器来回收。可以通过-XX:CMSInitiatingOccupancyFraction: 参数来设置当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)。
G1收集器
如图所示,G1垃圾收集器是按相等的区域(Region)进行划分内存的,默认情况下,会把堆分成(最大)2048个区域,也可以自己设置。如果堆内存为8G,则每个区域大小为4M,G1垃圾收集器虽然也是基于分代理论,但是,它每个分代的区域是混杂的,并且每个区域是不固定的,可以经过一次回收之后该区域就从年轻代变成了老年代。
G1底层是用的标记-复制算法,即如果该区域对象仍存活,标记下来,然后复制到另外的区域中,所以收集一次之后并没有内存碎片产生。G1总体上使用标记标记-清除算法。
G1把堆分为:Eden区、survivor区、old区、humongous区(大对象区)。
年轻代:默认占总区域的5%,即上述大概100个Region。随着程序的运行,年轻代的区域会越来越多,最多不能超过总区域的60%(可以自己设置)。年轻代中Eden区与survivor区的比例默认为8:1:1,一个区域属于某个分代是不确定的,该region原来属于Eden区,经过一次垃圾回收之后可能就变成了old区。
Humongous区:该区域是由多个region组合连接而成。只要一个对象大小超过了每个区域的50%,该对象就会被视为大对象,放入hunongous区,不会放入老年代,减少老年代的负担,并且minor gc(young gc)时不会去回收该区域,只在mixed gc或full gc时对该区域进行回收。
G1垃圾回收器的回收机制:
初始标记:会STW,并记录jc root能直接引用的对象,并标记。
并发标记:同cms并发标记。
最终标记:同cms重新标记。
筛选回收:会STW,G1会维护一个对每个区域的对象进行收集所花费的时间与收集的大小的列表,来推算出收集每个区域的性价比,同时,G1支持用户自定义输入STW时间,G1根据用户输入的时间再与上述维护的表中进行比较,来进行垃圾对象收集处理。所以,根据用户的时间来进行垃圾收集区域的筛选,这样会造成一个现象,就是每次回收如果时间过短,垃圾对象不会被回收完全,只会被回收一部分,所以。用户不能把时间设置太短,不然每次回收一小部分垃圾对象,会导致频繁的minor gc,最终导致full gc。
G1垃圾收集器分类:
young gc:当年轻代eden区放满之后,G1会判断此次Eden区垃圾对象回收所花费的时间与用户设定的停顿STW时间相比较,如果时间远远小于,则不进行young gc,并且继续给eden区加区域,直到下次放满之后预测的回收时间与设定值相近,才进行回收。
mixed gc:随着程序的运行,老年代的区域region会越来越多,如果老年代的区域占总区域的比例达到一定值(用户可设置),就会触发mixed gc,触发之后,G1会判断收集年轻代和部分老年代以及大对象区域所花费的时间与用户设定的STW时间相比较,来确定老年代回收的垃圾对象的多少。在此期间,如果没有足够的内存去复制对象存放,就会触发full gc。
full gc:会STW,然后单线程回收所有区域,供下一次mixed gc 使用。效率低,耗时多。
zgc收集器
不分代、停顿时间短、支持堆内存大。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。