垃圾回收算法
- 标记-清除算法:分为标记和清除两个阶段:首先标记出所有需要回收的对象,标记完成后进行统一的清除回收所有被标记的对象。
**缺点:
1)效率问题:标记和清除这两个过程的效率都很低
2)空间问题:产生大量不连续的内存碎片(空间碎片太多可能会导致后面程序运行过程中需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集) - 复制算法(新生代回收算法):将内存区域分成两个区域,每次只使用其中的一块,当这块内存要进行垃圾回收时,会将此区域上的存活的对象复制到另外一块上面,然后把使用过的那块内存区域全部清理掉。
优点: 对整个半区进行内存回收,提高了效率,内存分配时不用考虑空间碎片的问题(现在的商用虚拟机,包括HotSpot都是采用这种方法来回收新生代)
新生代中大部分对象都不会存活很久,将新生代内存分为一块较大的Eden区和两块较小的Survivor区(一个叫From,一个叫To)回收的时候,将Eden区和其中一块Survivor中还存活的对象一次性复制到另一块Survivor区上,然后将Eden区和另一块Survivor区清除。
当Survivor区不够用的时候需要老年代进行分配担保。
HotSpot默认Eden和Survivor区的比例是8:1:1 - **标记-整理算法(老年代回收算法):**复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。所以在老年代不能使用复制算法。
标记过程与“标记-清除”过程一致,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。
- **分代收集算法:**根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,新生代中,大量对象在垃圾回收时死去,只有少量存活,因此采用复制算法,而老年代中对象存活率高,没有额外空间对它进行分配担保,就要采用标记-清除或者标记-整理算法。
Minor GC 和Full GC 有什么不同?
1)Minor GC又称为新生代GC:发生在新生代的垃圾收集。Minor一般采用复制算法,一般回收速度比较快。
2)Full GC 指的是老年代GC,又称为Major GC,发生在老年代的垃圾收集。出现了Major GC 经常会伴随至少一次Minor GC,并非绝对,Major GC 比Minor GC慢10倍以上。
大对象的回收是Full GC(老年代回收)
垃圾收集器
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
新生代收集器:
-
Serial收集器(串行GC)
新生代的单线程收集器,使用一个CPU或者一条收集线程区完成垃圾收集工作,更重要的是,在它运行垃圾收集工作时,必须暂停其他所有工作线程(stop the world)直到他收集结束。是虚拟机运行在Client模式下的默认新生代收集器。
优点:简单高效,没有线程交互的开销,采用的算法是复制算法。 -
parNew收集器:(并行GC)
其实就是Serial收集器的多线程版本,是HotSpot虚拟机中第一个真正意义上的并发收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作,也是复制算法实现。
应用场景:是许多运行在Server模式下的虚拟机中首选的新生代收集器。 -
Parallel Scavenge收集器(新生代收集器,并行GC)
使用复制算法。
直观上,只要最大的垃圾收集停顿时间越少,吞吐量越高;但是GC停顿时间的缩短是以牺牲吞吐量和新生代空间作为代价的。
应用:适合在后台运算不需要进行太多交互的任务。
Parallel Scavenge和CMS等收集器:Parallel Scavenge收集的目标是达到一个可控制的吞吐量,称为“吞吐量优先收集器”,CMS等收集的目标是尽可能的缩短垃圾收集时用户线程的停顿时间。
Parallel Scavenge和PerNew:Parallel Scavenge具有自适应调节策略。
GC的自适应调节策略:Parallel Scavenge有一个参数-XX:+useAdaptivesSizePolicy。当这个参数打开后,不需要手工指定新生代大小、Eden和Survivor区的分区比例,晋升老年代的年龄等细节参数,虚拟机会根据当前的系统运行情况进行动态调整提供最合适的停顿时间或者最大吞吐量。
老年代收集器:
-
Serial Old收集器(串行GC)
单线程,使用标记-整理算法
应用:1)Client 2)Server:在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用;CMS收集器的后预案,在并发收集发生Concurrent Mode Failure时使用。 -
Parallel Old收集器(并行GC)
多线程,基于标记-整理算法
JDK1.6以后提供,因为如果新生代使用Parallel Scavenge收集器进行垃圾收集,那么老年代只能使用Serial Old收集器进行垃圾收集(Parallel Scavenge无法与CMS搭配使用)会导致吞吐量降低。而Paraller Old出现后,“吞吐量优先”收集器有了最名副其实的应用组合。 -
CMS收集器(并发GC)
是一种以获取最短回收停顿时间为目的的收集器,适用于重视吞吐量以及CPU敏感的场合。
基于“标记-清除”算法,步骤:
1.初始标记:标记一下GC Roots能直接关联到的对象‘
2.并发标记:进行GC Roots Tracing的过程
3.重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。
4.并发清除
在标记清除算法中,初始标记和重新标记依然要Stop the world。
CMS收集器的内存回收是与用户线程一起并发执行的。
优点:并发收集、低停顿。
缺点:1.对CPU资源敏感,在并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量降低。
2.CMS收集器无法处理浮动垃圾,可能会出“Concurrent Mode Failure”失败而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行着,伴随着程序就还会有新的垃圾产生,这一部分出现在标记的后面,所以没办法在当前收集中清理掉他们,只能在下一次GC时清理,这也就需要预留有足够的内存空间给用户线程使用。
3.它是基于“标记-清除”算法的,会产生大量的空间碎片。
唯一一个全区域的垃圾回收器------G1收集器
G1收集器实在JDK1.7开始使用,在JDK1.9设置为默认垃圾收集器,G1提供了三种模式的垃圾回收
-
Minor GC
当eden区满的时候,会触发一次Minor GC,这种触发机制和之前的垃圾收集器差不多,都是将Eden区
和其中一个Survivor区的存活对象拷贝到另外一个Survivor区或者晋升到Old区,然后清空Eden区和Survivor区 -
Mixed GC
当越来越多的年轻代中的对象晋升到老年代,会触发一次混合的垃圾收集器。也就是除了回收老年代,也会回收年轻代,这里是一部分老年代,可以选择哪些老年代的对象需要收集。 -
Full GC
如果对象内存分配的过快,mixed GC来不及回收,老年代被填满,就会触发Full GC.G1的Full GC,就是serial old GC,会暂停工作线程。
G1收集器工作过程四个步骤:
1.初始标记,整个过程STW,标记了从GC Roots的可达对象
2.并发标记,整个过程用户线程的垃圾回收线程共同执行,标记GC Roots可达对象的关联对象,收集整个Region的存活对象。
3.最终标记,整个过程STW,标记处并发标记遗漏的,以及引用关系发生变化的存货对象。
4.筛选回收,垃圾清理过程,如果整个Region没有存活对象,将Region就到存活列表当中。
G1收集器的特点:
1.并行与并发:G1能充分利用多CPU、多核的硬件优势,来缩短stop-the-world停顿时间
2.分代收集:和其他收集器相同,分代概念依然保留,G1收集器不需要其他收集器的配合就能管理整个堆,可以根据不同的方式去处理新创建的对象、存活了一段时间的对象和经历过多少次GC的对象。
3.空间整合:采用标记-整理的方式处理碎片化空间。这种方式意味着G1收集器运作期间不会出纳何时能内存的空间碎片,收集后提供规整的可用空间。
4.可预测的停顿:是G1相对于CMS另外一个更大的优势,能够使开发者明确指定在长度为m毫秒的时间片段内,消耗在垃圾收集器上的时间不能超过m毫秒。他之所以能建立可预测的停顿时间模型,是因为他可以有计划的避免在整个java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要的时间的经验值),在后台维护一个优先队列,每次根据允许的收集时间,优先回收价值最大的Region(这也是Garbage-First名称的由来)。这种使用Region划分空间以及有有点急区域回收的方式,保证了G1收集器在有限时间内获取尽可能高的收集效率。
使用G1收集器时,java堆的内存布局就与其他收集器有很大的区别,他将整个java堆或分为多个大小不等的独立区域(Region),虽然还保留有新生代和老年代的概念,但是新生代和老年代不再是物理隔离的了,他们是一部分的Region(不需要连续)的集合。
在G1收集器中,Region之间的对象引用以及其在他的收集器中的新生代以老年代之间的对象引用,虚拟机都是使用Remmembered Set来避免全堆扫描的。G1中的每个Region都有一个与之对用的Remmembered Set,虚拟发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处在不同的Region中,(在分代例子中就是检查是否有老年代中的对象引用了新生代对象)如果是,就通过CardTable把相关引用信息记录到被引用的对象所属的Region的Remembered set之中,当进行内存回收时,在GC根节点的枚举范围中加入Remebered Set即可保证不对全堆扫描也不会有遗漏。
GC日志
[GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs] [Full GC [Tenured: 0K->210K(10240K), 0.0149142 secs] 4603K->210K(19456K), [Perm : 2999K>2999K(21248K)], 0.0150007 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
-
GC日志开头GC/Full GC 说明了这次垃圾收集器的停顿类型,并不是区分老年代/新生代,FullGC表示本次GC发生了STW(StopTheWorld);调用System.gc()触发的垃圾收集,则显示Full GC(System)
-
接下来表示GC发生的区域:DefNew:Serial收集器;
-
方括号里面的表示GC前该内存区域已使用的容量->GC后该内存区域已使用的容量
方括号外面的表示GC前堆已使用容量->GC后堆已使用容量(Java堆的总容量) -
该区域内存GC所使用的时间,单位为秒