垃圾收集概述GarbageCollection通常被称为“GC”,诞生于1960年MIT的Lisp语言,经过半个多世纪,如今已经非常成熟。在jvm中,程序计数器、虚拟机栈、本地方法栈随线程一起诞生和销毁,栈帧随着方法的进入和退出而被压入和弹出,实现了内存的自动清理。因此,我们的内存垃圾回收主要集中在java堆和方法区。在程序运行过程中,这部分内存的分配和使用是动态的。判断一个对象是否存活一般有两种方式:引用计数:每个对象都有一个引用计数属性。当添加引用时,count加1,释放引用时,count减1,当count为0时,可以回收。这种方法比较简单,不能解决对象间循环引用的问题。ReachabilityAnalysis:从GCRoots开始向下搜索,搜索所经过的路径称为引用链。当一个对象没有任何引用链连接到GCRoots时,证明该对象不可用。无法到达的对象。在Java语言中,GCRoots包括:虚拟机栈中引用的对象。方法区中类静态属性实体所引用的对象。方法区常量所引用的对象。JNI在本机方法栈中引用的对象。GarbageCollectionAlgorithmMark-SweepAlgorithm“标记-清除”(Mark-Sweep)算法,正如它的名字一样,分为两个阶段:“标记”和“清除”。完成后统一收集所有标记对象。之所以说它是最基础的集合算法,是因为后来的集合算法都是基于这个思想,在其不足之处进行改进。它有两个主要缺点:一是效率问题,标记和清除过程的效率不高;另一种是空间问题,标记和清除后会产生大量不连续的内存碎片,过多的空间碎片可能会导致,当程序在后面的运行过程中需要分配更大的对象时,找不到足够的连续内存并且必须提前触发另一个垃圾收集动作。Copyingalgorithm“Copying”(复制)收集算法,将可用内存按容量分成大小相等的两块,每次只使用其中一块。当这块内存用完后,将存活的对象复制到另一块中,然后一次性清理掉已使用的内存空间。这样每次就回收一块内存,分配内存时就不用考虑内存碎片等复杂情况。只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存减少到原来的一半,持续复制长寿命对象会导致效率降低。标记压缩算法复制收集算法在对象存活率高的时候会进行更多的复制操作,效率会变低。更重要的是,如果不想浪费50%的空间,就需要有额外的空间分配保证来应对已用内存中所有对象都是100%存活的极端情况,所以一般不能直接在老年代算法中选择这个。根据老年代的特点,有人提出了另一种“Mark-Compact”算法。标记过程还是和“Mark-Clear”算法一样,只是后面的步骤不是直接清理可回收对象,而是让所有存活的对象移动到一端,然后直接清理超出该端的内存边界。分代收集算法GC分代的基本假设:大多数对象的生命周期很短,生存时间很短。“分代收集”算法将Java堆分为新生代和老年代,这样可以根据每个年代的特点采用最合适的收集算法。在新生代中,发现每次垃圾回收都有大量对象死亡,只有少量对象存活。然后采用复制算法,只需付出复制少量存活对象的代价即可完成收集。在年老代中,由于对象存活率高,没有额外的空间为其分配保证,因此需要使用“标记-清理”或“标记-组织”算法进行回收。垃圾收集器如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现串行收集器串行收集器是最古老、最稳定、最高效的收集器,可能会造成长时间的停顿。只使用一个线程来回收。新生代和老年代使用串行回收;新生代复制算法,老年代标记压缩;垃圾回收进程会停止世界(服务暂停)参数控制:-XX:+UseSerialGCserialcollectorParNewcollectorParNew收集器实际上是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法,老年代标记压缩参数控制:-XX:+UseParNewGCParNew收集器-XX:ParallelGCThreads限制线程数Parallel收集器ParallelScavenge收集器类似于ParNew收集器,Parallel收集器更关注系统的吞吐量。可通过参数开启自适应调整策略,虚拟机根据当前系统运行状态收集性能监控信息,动态调整这些参数,提供最合适的停顿时间或最佳吞吐量;GC时间也可以通过参数控制,不超过几毫秒或者比例;newgenerationcopyalgorithm,oldagemark-compressionparametercontrol:-XX:+UseParallelGC使用Parallelcollector+oldgenerationserialParallelOldcollectorParallelOld是老年代版本的ParallelScavengecollector,使用多线程和“mark-and-sort”算法。这个收集器在JDK1.6才开始提供参数控制:-XX:+UseParallelOldGC使用并行收集器+老年代并行CMS收集器CMS(ConcurrentMarkSweep)收集器是一个以回收停顿时间最短为目标的收集器。目前,很大一部分Java应用都集中在互联网网站或B/S系统的服务器端。这类应用特别注重服务的响应速度,希望系统停顿时间最短,从而给用户带来更好的体验。从名字(包括“MarkSweep”)可以看出CMS收集器是基于“mark-clear”算法实现的。它的操作过程比以前的收集器更复杂。整个过程分为4个步骤,包括:初始标记(CMSinitialmark)并发标记(CMSconcurrentmark)备注(CMSremark)并发清除(CMSconcurrentsweep)初始标记和备注两个步骤还需要“停止”世界”。初始标记只是标记GCRoots可以直接关联到的对象,速度很快。并发标记阶段是GCRootsTracing的过程,重标记阶段是纠正并发标记期间用户程序继续运行造成的标记。对象发生变化的部分的标记记录,这个阶段的暂停时间一般比初始标记阶段稍长,但比并发标记时间短很多。由于收集器线程在整个过程中最长的并发标记和并发清除过程中可以和用户线程一起工作,所以一般情况下,CMS收集器的内存回收过程是和用户线程并发执行的。老年代收集器(新生代使用ParNew)优点:并发收集,低暂停缺点:产生大量空间碎片,并发阶段会降低吞吐量参数控制:-XX:+UseConcMarkSweepGC使用CMS收集器-XX:+UseCMSCompactAtFullCollectionAfterFullGC,执行碎片整理;碎片整理过程是排他性的,这会导致停顿时间变长(可用CPU数量)G1收集器G1是当前技术发展最前沿的成果之一。HotSpot开发组所赋予的使命是在未来替代JDK1.5发布的CMS收集器。与CMS收集器相比,G1收集器有以下特点:空间整合,G1收集器使用标记整理算法,不会产生内存空间碎片。在分配大对象时,不会因为找不到连续的空间而提前触发下一次GC。可预测的暂停是G1的另一大优势。减少停顿时间是G1和CMS共同关心的问题。不过,除了追求低停顿之外,G1还可以建立一个可预测的停顿时间模型,让用户明确指定当在一个长度为N毫秒的时间段内,不应该花费超过N毫秒的时间进行垃圾回收,这几乎是一个实时Java(RTSJ)垃圾收集器的功能。上面说的垃圾收集器,收集范围是整个新生代或者老年代,但是G1就不是这样了。使用G1收集器时,Java堆的内存布局与其他收集器有很大不同。它将整个Java堆划分为多个大小相等的独立区域(Regions)。虽然它仍然保留了新生代和老年代的概念,但是新生代和老年代在物理上不再是分离的,它们都是(可以不连续的)一个Region集合的一部分。G1的新一代集合类似于ParNew。当新生代占用率达到一定比例时,开始收集。与CMS类似,G1收集器在老年代收集对象时会有短暂的停顿。收集步骤:1.标记阶段,首先是初始标记(Initial-Mark),这个阶段是暂停(StoptheWorldEvent),会触发一次正常的MintorGC。对应GClog:GCpause(young)(inital-mark)2、RootRegionScanning,survivor区在程序运行过程中会被回收(survivingtotheoldage),这个过程必须在young之前完成GC。3.ConcurrentMarking,在整个堆上进行并发标记(与应用程序并发执行),这个过程可能会被youngGC打断。在并发标记阶段,如果发现区域对象中的所有对象都是垃圾,则立即回收该区域(图中的X)。同时,在并发标记过程中,计算每个区域的对象活跃度(区域内存活对象的比例)。4、remark,然后mark,会有一个短暂的停顿(STW)。重新标记阶段用于收集并发标记阶段产生的新垃圾(并发阶段与应用程序一起运行);G1使用比CMS更快的初始快照算法:snapshot-at-the-beginning(SATB)。5.Copy/Cleanup,多线程清除非活动对象,会有STW。G1将回收区中存活的对象复制到新区,清除RememberSets,并发清除回收区返回到空闲区链表。6.复制/清除过程后。回收区内的活动物体已被集中回收到深蓝色和深绿色区域。收藏家常用组合【本文为专栏作家《纯洁的微笑》原创稿件,转载请微信联系作者♂获得授权】点此查看作者更多好文
