1.什么是GC?GC(GarbageCollection)垃圾收集,顾名思义就是专门用来回收垃圾的,在C/C++中,当我们需要使用内存时,需要先手动声明,使用完后需要手动回收。这两部分很麻烦,经常会出现这方面的问题。而这一切都已经在Java中自动执行了,所以我们在写代码的时候就不用担心这些无效的数据了。2.GC分类在目前主流的虚拟机中,大部分都是按照分代收集的理论来设计的。因为虚拟机中的大部分对象都是有生有死的,而在多次垃圾回收中存活下来的对象是比较难被回收的。因此,之前理论堆分为两个区域,新生代和老年代。前者主要存储即将消亡的对象,后者存储难以消亡的对象。1.MinorGC/YoungGC:指的只是新生代的回收。2.老年代收集(MajorGC/OldGC):指的只是老年代的收集。目前只有CMS垃圾收集器具有这种独立的老年代行为。(MajorGC的定义比较混乱,有的说是指老年代,有的说是整个堆的收集,这个需要根据别人的场景来定,没有固定的说法)3.全堆回收(FullGC):收集整个J??ava堆和方法区(注意包括方法区)3.垃圾收集算法1.复制算法(Copying)将一块内存区分成两半,当一半内存用完,把存活的对象放入另一半内存区,回收原来的内存区,不管内存碎片区,只要按顺序分配内存即可。实现简单,运行高效。但是这样也有一个缺点,就是内存的利用率只有50%,所以在JVM中有如下解决方案:Appel式回收添加Eden区,一般来说,内存区域分配为:Eden:80%,Survivor:20%(From10%,To10%),当Survivor区域不够用时,需要老年代进行分配保证。2.标记-清除方法(Mark-Sweep)该算法分为“标记”和“清理”两个阶段:第一步扫描需要标记所有可以回收的对象,第二遍扫描需要由第一步对象进行清洗和标记,效率略低。由于需要大量标记对象和清除,回收效率不复制算法。如果大部分对象都是活的和死的,那么被标记的对象会比较多,效率会比较低。还有一个比较大的问题就是会产生大量的内存碎片,导致大对象无法被标记存储,因此必须提前触发其他垃圾收集。3.标记紧凑法(Mark-Compact)步骤同clear方法。但是,它的第二步是组织除标记以外的所有对象,将所有对象向前移动并直接清除这些对象所在位置以外的对象。内存区域。记号法不会产生内存碎片,但是效率低。排序法和清空法的主要区别在于,一个是回收对象,一个是整理对象,移动对象也会需要暂停所有业务线程,然后更新所有对象引用(需要调整直接指针)。4.JVMGarbageCollector1.Serial/SerialOldJVM早期使用的垃圾收集器是单线程的,独占的,适合单CPU。只适合几十兆到几百兆的堆内存。如果超过这个内存的大小,回收效率会大大降低,所以目前用处不大。StopTheWorld(STW)当单个线程执行垃圾收集时,所有工作线程都必须挂起,直到它完成回收。这种暂停叫做“StopTheWorld”,但是这种STW带来了不好的用户体验,例如:应用程序每运行一小时就需要暂停5分钟。这也是早期的JVM和java被C/C++语言诟病性能不佳的重要原因。所以JVM开发团队一直在努力消除或减少STW时间。2.Parallel/ParallelOld为了提高JVM的回收效率,从JDK1.3开始,JVM使用了多线程的垃圾收集器,专注于吞吐量,可以更高效的使用CPU时间,从而完成程序尽快计算任务。所谓吞吐量是CPU运行用户代码所花费的时间与CPU总消耗时间的比值,即吞吐量=用户代码运行时间/(用户代码运行时间+垃圾回收时间),虚拟机一共运行了100分钟,其中Garbagecollection耗时1分钟,也就是99%的吞吐量。这个垃圾回收器适用于回收几百兆到几千兆的堆空间。JVM参数设置JDK1.8默认是如下组合-XX:+UseParallelGC新生代使用ParallelScavenge,老年代使用ParallelOld-XX:MaxGCPauseMillis但是不要以为这个参数的值设置小一点,系统垃圾收集速度变快,以吞吐量和新生代空间为代价缩短垃圾收集停顿时间:系统将新生代调整得更小,收集300MB新生代肯定比收集500MB快,但是这也是它直接导致垃圾收集更频繁地发生。以前是每10秒采集一次,停顿100毫秒,现在是每5秒采集一次,停顿70毫秒。暂停时间确实下降了,但吞吐量也下降了。-XX:GCTimeRatio-XX:GCTimeRatio参数的值应该是一个大于0小于100的整数,即垃圾回收时间占总时间的比例,相当于吞吐量的倒数。例如:如果该参数设置为19,则最大允许垃圾回收占用总时间的5%(即1/(1+19)),默认值为99,即允许最大占总时间的1%(即1/(1+99))由于垃圾收集时间与吞吐量密切相关,ParallelScavenge是一个“吞吐量优先的垃圾收集器”。-XX:+UseAdaptiveSizePolicy-XX:+UseAdaptiveSizePolicy(默认启用)。这是一个开关参数。激活该参数后,无需手动指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、老年代对象的大小(-XX:PretenureSizeThreshold)等详细参数,虚拟机根据当前系统运行状态收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大吞吐量。3.ParNew/CMSParNew多线垃圾收集器类似于Parallel,唯一的区别是:多线程,多CPU,停顿时间比Serial少。(JDK9之后,ParNew被并入CMS)。ConcurrentMarkSweep(CMS)这类垃圾收集器是以追求最短的收集暂停时间(STW)为目标。目前,很大一部分Java应用集中在互联网或B/S系统的服务器端。这些应用更注重服务的响应速度,希望暂停时间更短,以提升用户体验。MarkSweep从名字就可以看出,这个采集器采用的是mark-sweep方式。而且它的步骤比之前的收集器麻烦。整体流程分为4个步骤:初始标记:只标记与GCRoot直接相关的对象,这类对象较少,标记速度快。并发标记:标记所有与初始化标记对象关联的对象。这样的对象比较多,所以采用并发,和用户线程一起运行。重标记:纠正那些在并发标记时引起变化的对象标记。这个时间比初始标记稍长,但比并发标记快得多。并发清理:与用户线程一起运行,回收对象。-XX:+UseConcMarkSweepGC,表示新生代使用ParNew,老年代使用CMS。缺点:CPU敏感:因为使用的并发技术,对处理器的核心要求比较大。浮动垃圾:CMS进行并发清零时,由于使用并发和轻量,用户线程在清零时会产生新的垃圾.所以在回收的时候,需要预留一部分空间来存放这些产生的垃圾(JDK1.6设定的阈值是92%)。但是如果用户线程产生的垃圾比较快,预留内存无法存放时会出现ConcurrentModeFailure,虚拟机会临时启用SerialOld来代替CMS。内存碎片:因为mark-clear使用方法,会产生内存碎片。特点:总体来说,由于CMS是JVM产生的第一个并发垃圾收集器,所以还是比较有代表性的。为什么要用mark-clear的方式,因为如果在实现CMS的时候还组织对象,那么需要挂起业务线程,组织一个对象,那么STW的时间会比较长。为了追求STW时间,没有使用mark-organize。但是最大的问题是CMS使用了mark-and-clear算法,所以会出现内存碎片。当碎片较多时,会给大对象的分配带来很多麻烦。为了解决这个问题,CMS提供了一个参数:-XX:+UseCMSCompactAtFullCollection,一般是开启的,如果无法分配大对象,就会进行内存碎片处理。这个地方一般使用SerialOld,因为SerialOld是单线程的,所以如果内存空间大,对象多,出现这种情况CMS会卡住。这种垃圾收集器适用于回收几G的堆空间~20G左右。4.GarbageFirst(G1)G1垃圾收集器的设计思路不同于之前所有的垃圾收集器。以前的垃圾收集器都是按照分代的方式设计的,而G1把堆当作一个整体的区域来考虑,把这个区域分成大小相同的独立区域(Regions),每个区域可以起到Eden、Survivor的作用,以及需要的老年代区域。在进行对象恢复时,可以根据各个区域的情况进行恢复,提高效率。Region上面说了,每个Region除了可以起到不同的作用之外,还有一个类似老年代的Humongous区域,用来存放大对象。当一个对象超过Region空间的一半大小时,就判定为大对象。(每个Region的大小可以通过参数-XX:G1HeapRegionSize设置,取值范围1MB~32MB,应该是2的N次方。)对于那些超过整个Region容量的超大对象,它将存储在连续的N个HumongousRegions中,并且在大多数情况下G1回收HumongousRegions作为老年代的一部分。启用参数:-XX:+UseG1GC`分区大小:-XX:+G1HeapRegionSize一般建议逐渐增加这个值。随着大小的增加,垃圾存活的时间会更长,GC间隔时间也会更长,但每次GC的时间也会增加更长。最大GC暂停时间:-XX:MaxGCPauseMillis运行过程G1的运行过程大致可以分为以下四步:初始标记(InitialMarking):标记可以关联GCRoots的对象,修改TAMS的值指针,这个过程需要挂起用户线程,但是时间很短。TAMS(TopatMarkStart):当进行下一步并发标记时,用户线程会产生新的对象,而这些对象被判断为可存活对象而不是垃圾。这时候就需要划分一个小区域来存放这些对象。并发标记:扫描并标记所有回收的对象。当扫描完成后,会同时存在引用发生变化的对象,而这些对象会丢失。这些丢失的对象将通过SATB算法解决。SATB(snapshot-at-the-beginning):类似于快照,保存当前区域的快照,然后在最后的标记处比较并检查丢失的标记。重新标记(后面的文章会详细解释)。FinalMarking:暂停所有用户线程,标记之前遗漏的对象。筛选回收(LiveDataCountingandEvacuation):更新Region的统计数据,排序回收每个Region的值,根据用户设置的暂停时间制定回收计划,自由选择任意Region进行回收。将需要回收的Region复制到一个空Region中,然后清除整个原来的Region。这块还涉及到对象的移动,所以需要挂起所有用户线程,有多个recycler线程来完成。特点:并行与并发:G1在多CPU、多核环境下可以充分利用硬件优势,使用多个CPU(CPU或CPU核)来缩短stop-the-world暂停时间。其他一些收集器原本需要停止Java线程执行的GC动作,G1收集器仍然可以让Java程序以并发的方式继续执行。分代收集:与其他收集器一样,G1中仍然保留了分代概念。虽然G1可以独立管理整个GC堆而不需要其他收集器的配合,但是它可以使用不同的方法来处理新创建的对象和存活了一段时间并存活了多次GC的旧对象以获得更好的结果。收集效果。空间整合:与CMS的“mark-clean”算法不同,G1是整体基于“mark-sort”算法的收集器,在局部(两个之间)基于“copy”算法地区)。不管怎样,这两种算法都意味着G1在运行过程中不会产生内存空间碎片,回收后可以提供规律的可用内存。这个特性有利于长时间运行的程序,在分配大对象时,不会因为找不到连续的内存空间而提前触发下一次GC。暂停时间的追求:-XX:MaxGCPauseMillis指定的最大暂停时间目标,G1尝试调整新生代与老年代的比例、堆大小和提升年龄来达到这个目标时间。垃圾收集器适用于回收数百GB的堆空间。一般在G1和CMS之间选择的话,平衡点在6~8G,只有内存比较大的G1才能发挥优势
