常用的垃圾回收算法
1.Mark-Sweep(标记-清除算法)
1.Mark-Sweep(标记-清除算法)

标记-清除算法和它的名字一样,算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收被标记的对象。
特点:1)标记-清除算法不会进行对象的移动,直接回收不存活的对象,因而会造成内存碎片
:2)虽然内存回收了但是很可能会发生OOM,如上图所示,假设现在要分配10个格子内存,虽然内存回收了,总的可用内存足够大,但是并没有连续的10个格子可用
2.Copying(复制算法)
复制算法,它是将可用内存均分为两块,每次只用其中的一块。当这块内存用完了,就将还存活的对象复制到另一块内存区域,然后把已使用过的内存空间清理掉
特点:1)不用考虑内存碎片情况。复制算法每次都是对某一块内存进行回收,做的是直接清除操作,所以只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。但是复制算法代价是将内存缩小为原来的一半,持续复制长期存活的对象导致效率降低
:2)复制回收算法在对象存活率较高的情况下会执行较多的复制操作,效率因此会降低,更关键的是,如果不想浪费50%的内存空间,就需要有额外的的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况
3.Mark-Compact(标记压缩算法)
标记压缩算法的标记过程和Mark-Sweep算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
特点:1)标记压缩算法可以解决内存碎片的问题,但是它也引用了额外的开销,比如说额外的空间来保存迁移地址、需要遍历多次堆内存等
4.分代收集算法
任何商业虚拟机的垃圾回收都是采用分代收集算法。分代的垃圾回收策略,是基于不同对象的生命周期是不一样的,因此,不同生命周期的对象可以采用不同的回收方式,以便提高效率。
在Java程序运行的过程中会产生大量的对象,这些对象的生命周期各有不同,如果不进行对象存活时间区分的话,每次垃圾回收都是对整个堆空间进行回收,那么消耗的时间相对会很长,而且对于存活时间较长的对象进行的扫描工作都是无用的,因此要对不同生命周期对象采用不同的垃圾回收机制,即分治思想。
现在主流的做法是将Java的堆分为新生代和老年代
新生代又被进一步划分为Eden和Survivor区,Survivor由From Space和To Space组成,这样划分的好处是为了更快的回收内存,根据不同的分代执行不同的回收算法
新生代:新建的对象都是用新生代分配内存,当Eden满时,会把存活的对象转移到两个Survivor中的一个,当一个Survivor满时会把不满足晋升的对象复制到另一个Survivor
晋升:对象每经历一次Minor GC(新生代中的gc)年龄+1,年龄达到设置的一个阈值后,被放入老年代
两个Survivor:目的是避免碎片。如果只有一个Survivor,那Survivor被执行一次gc之后,可能对象是A+B+C,经历一次gc后B被回收,则会A| |C,造成碎片
老年代:用于存放新生代中经历N次gc仍然存活的对象,老年代的垃圾回收成为Major GC,整堆包括新生代和老年代的垃圾回收称之为Full GC
永久代:主要存放所有已加载的类信息,方法信息,常量池等等。并不等同于方法区,只不过主流的公司比如Hotspot JVM用永久带来实现方法区而已,有些虚拟机没有永久带而用其它机制来实现方法区,这个区域存放的内容与垃圾回收要回收的java对象关系不大
总结:一般来说,在新生代中,每次垃圾回收时都发现有大批的对象死去,只有少量存活,所以一般选用复制算法,只需要付出少量存活对象的复制成本就可以完成回收;老年代中因为对象的存活率高,没有额外的空间进行分配担保,就必须使用Mark-Sweep或者Mark-Compact算法来进行回收
垃圾收集器:垃圾回收算法是内存回收的概念,垃圾收集器是内存回收的具体实现
Serial(串行收集器):Serial串行收集器是一个单线程的,在进行垃圾收集时,必须暂停其他所有的工作线程,直到收集结束才能继续执行。Serial串行收集器的缺点就是会Stop The World,也就是会把程序暂停,但是单线程也意味着它非常简单高效,没有多余的线程交互。在Client版本的java中是默认的新生代收集器。Serial Old收集器是老版本的串行收集器,采用的是标记压缩算法
ParNew收集器:ParNew收集器是Serial串行收集器的多线程版本,除了使用多线程进行垃圾收集外,其他的行为和Serial一样。ParNew收集器是Server版本的虚拟机中首选的新生代收集器,除了Serial就他可以和CMS配合
Parallel Scavenge收集器:同样是新生代的收集器,使用的是复制算法,并行的多线程收集器。Parallel Scavenge收集器与其它收集器的差异化地方在于Parallel Scavenge收集器的关注点在控制吞吐量,也就是CPU用于运行用户代码事件与CPU总消耗时间的比值,吞吐量越高表示垃圾回收时间占比越小,CPU利用率越高。所以Parallel Scavenge收集器也被称为“吞吐量收集器”,高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间
CMS收集器:Concurrent Mark Sweep收集器是一种以获得最短回收停顿时间为目标的收集器,也称为并发低停顿收集器或者低延迟垃圾收集器
CMS它分为4个步骤:
1)初始标记(CMS initial mark)
仅标记一下GC Roots能直接关联到的对象,速度很快,但需要Stop The World
2) 并发标记(CMS concurrent mark)
进行GC Roots追踪的过程,上一步标记出存活对象,但是应用程序也在运行,并不能保证可以标记出所有的存活对象
3)重新标记(CMS remark)
为了修正并发标记期间因程序继续运行而导致标记变动的那一部分对象的标记记录。需要Stop The World,且停顿时间比初始标记稍长,但远比并发标记短;此过程采用多线程并发执行来提升效率
4)并发清除(CMS concurrent sweep)
回收所有的垃圾对象
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程可与用户线程一起工作,所以,从整体来看,CMS收集器的内存回收过程是与用户线程一起并发执行的
CMS优点:并发收集 + 低停顿
CMS缺点:1.造成CPU资源紧张(开辟了多个线程)
2.无法处理浮动垃圾:由于CMS并发清理阶段用户线程还在运行,自然会有新的垃圾产生,这一部分垃圾出现在标记过程结束之后,CMS无法在当次收集中处理它们,只好留待下一次GC时再清理掉。这一部分垃圾就被称为“浮动垃圾”CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运行作用,要是CMS运行期间预留的内存无法满足程序需要,就会出现一次Concurrent Mode Failure,这时虚拟机将启动后备预案:临时启动Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了
3.大量内存碎片:来源Mark-Sweep算法
G1收集器
Garbage-First收集器是当今收集器技术房展最前沿的成果之一,是一款面向服务器端应用的垃圾收集器
Garbage-First和CMS差不多,但是Garbage-First的采集范围是整个堆(老年代和新生代),它把内存堆分成多个大小相等的独立区域,在最后筛选回收时根据这些区域的回收价值和成本决定是否回收掉该内存
Dalvik虚拟机主要使用Mark-Sweep算法,也可以选择使用Copy算法
Android在4.4中引入ART,ART是Android5.0以上默认的运行时。ART有多个不同的GC方案,这些方案包括运行不同垃圾回收器。默认是CMS方案,主要使用粘性CMS和部分CMS。除CMS方案外,当应用将进程状态改为察觉不到卡顿的进程状态(例如后台或缓存)时,ART将执行堆压缩
粘性CMS:粘性CMS是ART的不移动分代垃圾回收器,它仅仅扫描堆中自上次GC后修改的部分,并且只能回收自上次GC后分配的对象。
以上所述也是内存抖动为什么造成OOM的原因
特点:1)标记-清除算法不会进行对象的移动,直接回收不存活的对象,因而会造成内存碎片
:2)虽然内存回收了但是很可能会发生OOM,如上图所示,假设现在要分配10个格子内存,虽然内存回收了,总的可用内存足够大,但是并没有连续的10个格子可用
2.Copying(复制算法)

特点:1)不用考虑内存碎片情况。复制算法每次都是对某一块内存进行回收,做的是直接清除操作,所以只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。但是复制算法代价是将内存缩小为原来的一半,持续复制长期存活的对象导致效率降低
:2)复制回收算法在对象存活率较高的情况下会执行较多的复制操作,效率因此会降低,更关键的是,如果不想浪费50%的内存空间,就需要有额外的的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况
3.Mark-Compact(标记压缩算法)

特点:1)标记压缩算法可以解决内存碎片的问题,但是它也引用了额外的开销,比如说额外的空间来保存迁移地址、需要遍历多次堆内存等
4.分代收集算法
任何商业虚拟机的垃圾回收都是采用分代收集算法。分代的垃圾回收策略,是基于不同对象的生命周期是不一样的,因此,不同生命周期的对象可以采用不同的回收方式,以便提高效率。
在Java程序运行的过程中会产生大量的对象,这些对象的生命周期各有不同,如果不进行对象存活时间区分的话,每次垃圾回收都是对整个堆空间进行回收,那么消耗的时间相对会很长,而且对于存活时间较长的对象进行的扫描工作都是无用的,因此要对不同生命周期对象采用不同的垃圾回收机制,即分治思想。
现在主流的做法是将Java的堆分为新生代和老年代
新生代又被进一步划分为Eden和Survivor区,Survivor由From Space和To Space组成,这样划分的好处是为了更快的回收内存,根据不同的分代执行不同的回收算法

晋升:对象每经历一次Minor GC(新生代中的gc)年龄+1,年龄达到设置的一个阈值后,被放入老年代
两个Survivor:目的是避免碎片。如果只有一个Survivor,那Survivor被执行一次gc之后,可能对象是A+B+C,经历一次gc后B被回收,则会A| |C,造成碎片
老年代:用于存放新生代中经历N次gc仍然存活的对象,老年代的垃圾回收成为Major GC,整堆包括新生代和老年代的垃圾回收称之为Full GC
永久代:主要存放所有已加载的类信息,方法信息,常量池等等。并不等同于方法区,只不过主流的公司比如Hotspot JVM用永久带来实现方法区而已,有些虚拟机没有永久带而用其它机制来实现方法区,这个区域存放的内容与垃圾回收要回收的java对象关系不大
总结:一般来说,在新生代中,每次垃圾回收时都发现有大批的对象死去,只有少量存活,所以一般选用复制算法,只需要付出少量存活对象的复制成本就可以完成回收;老年代中因为对象的存活率高,没有额外的空间进行分配担保,就必须使用Mark-Sweep或者Mark-Compact算法来进行回收
垃圾收集器:垃圾回收算法是内存回收的概念,垃圾收集器是内存回收的具体实现



CMS收集器:Concurrent Mark Sweep收集器是一种以获得最短回收停顿时间为目标的收集器,也称为并发低停顿收集器或者低延迟垃圾收集器

1)初始标记(CMS initial mark)
仅标记一下GC Roots能直接关联到的对象,速度很快,但需要Stop The World
2) 并发标记(CMS concurrent mark)
进行GC Roots追踪的过程,上一步标记出存活对象,但是应用程序也在运行,并不能保证可以标记出所有的存活对象
3)重新标记(CMS remark)
为了修正并发标记期间因程序继续运行而导致标记变动的那一部分对象的标记记录。需要Stop The World,且停顿时间比初始标记稍长,但远比并发标记短;此过程采用多线程并发执行来提升效率
4)并发清除(CMS concurrent sweep)
回收所有的垃圾对象
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程可与用户线程一起工作,所以,从整体来看,CMS收集器的内存回收过程是与用户线程一起并发执行的
CMS优点:并发收集 + 低停顿
CMS缺点:1.造成CPU资源紧张(开辟了多个线程)
2.无法处理浮动垃圾:由于CMS并发清理阶段用户线程还在运行,自然会有新的垃圾产生,这一部分垃圾出现在标记过程结束之后,CMS无法在当次收集中处理它们,只好留待下一次GC时再清理掉。这一部分垃圾就被称为“浮动垃圾”CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运行作用,要是CMS运行期间预留的内存无法满足程序需要,就会出现一次Concurrent Mode Failure,这时虚拟机将启动后备预案:临时启动Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了
3.大量内存碎片:来源Mark-Sweep算法
G1收集器
Garbage-First收集器是当今收集器技术房展最前沿的成果之一,是一款面向服务器端应用的垃圾收集器

Dalvik虚拟机主要使用Mark-Sweep算法,也可以选择使用Copy算法
Android在4.4中引入ART,ART是Android5.0以上默认的运行时。ART有多个不同的GC方案,这些方案包括运行不同垃圾回收器。默认是CMS方案,主要使用粘性CMS和部分CMS。除CMS方案外,当应用将进程状态改为察觉不到卡顿的进程状态(例如后台或缓存)时,ART将执行堆压缩
粘性CMS:粘性CMS是ART的不移动分代垃圾回收器,它仅仅扫描堆中自上次GC后修改的部分,并且只能回收自上次GC后分配的对象。
以上所述也是内存抖动为什么造成OOM的原因