最基础的收集算法是“标记-清除”(Mark-Sweep)算法，分两个阶段：首先标记出所有需要回收的对象，在标记完成后统一回收所有被标记的对象。
不足：一个是效率问题，标记和清除两个过程的效率都不高；另一个是空间问题，标记清除之后会产生大量不连续的内存碎片，空间碎片太多可能导致以后在程序运行过程需要分配较大对象时，无法找到足够的连续内存而不得不提前触发另一个的垃圾收集动作。
为了解决效率问题，一种称为复制(Copying)的收集算法出现了，它将可用内存按容量划分为大小相等的两块，每次只使用其中的一块。当这一块内存用完了，就将还存活着的对象复制到另外一块上，然后再把已经使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收，内存分配时也就不用考虑内存碎片等复杂情况，只要移动堆顶指针，按顺序分配内存即可，实现简单，运行高效。代价是内存缩小为原来的一半。
商业虚拟机用这个回收算法来回收新生代。IBM研究表明98%的对象是“朝生夕死“，不需要按照1-1的比例来划分内存空间，而是将内存分为一块较大的”Eden“空间和两块较小的Survivor空间，每次使用Eden和其中一块Survivor。当回收时，将Eden和Survivor中还存活的对象一次性复制到另外一个Survivor空间上，最后清理掉Eden和刚才用过的Survivor空间。Hotspot虚拟机默认Eden和Survivor的比例是8-1.即每次可用整个新生代的90%,只有一个survivor，即1/10被”浪费“。当然，98%的对象回收只是一般场景下的数据，我们没有办法保证每次回收都只有不多于10%的对象存活，当Survivor空间不够时，需要依赖其他内存(老年代)进行分配担保(HandlePromotion).
如果另外一块survivor空间没有足够空间存放上一次新生代收集下来的存活对象时，这些对象将直接通过分配担保机制进入老年代。
EdenSpace字面意思是伊甸园，对象被创建的时候首先放到这个区域，进行垃圾回收后，不能被回收的对象被放入到空的survivor区域。
SurvivorSpace幸存者区，用于保存在edenspace内存区域中经过垃圾回收后没有被回收的对象。Survivor有两个，分别为ToSurvivor、FromSurvivor，这个两个区域的空间大小是一样的。执行垃圾回收的时候Eden区域不能被回收的对象被放入到空的survivor（也就是ToSurvivor，同时Eden区域的内存会在垃圾回收的过程中全部释放），另一个survivor（即FromSurvivor）里不能被回收的对象也会被放入这个survivor（即ToSurvivor），然后ToSurvivor和FromSurvivor的标记会互换，始终保证一个survivor是空的。
为啥需要两个survivor？因为需要一个完整的空间来复制过来。当满的时候晋升。每次都往标记为to的里面放，然后互换，这时from已经被清空，可以当作to了。
复制收集算法在对象成活率较高时就要进行较多的复制操作，效率将会变低。更关键的是，如果不想浪费50%的空间，就需要有额外的空间进行分配担保，以应对被使用的内存中所有对象都100%存活的极端情况，所以，老年代一般不能直接选用这种算法。
根据老年代的特点，有人提出一种”标记-整理“Mark-Compact算法，标记过程仍然和标记-清除一样，但后续步骤不是直接对可回收对象进行清理，而是让所有存活的对象都向一端移动，然后直接清理端边界以外的内存.
当前商业虚拟机的垃圾收集都采用”分代收集“(GenerationalCollection)算法，这种算法根据对象存活周期的不同将内存划分为几块。一般把Java堆分为新生代和老年代，这样就可以根据各个年代的特点采用最适当的收集算法。在新生代，每次垃圾收集时都发现大批对象死去，只有少量存活，那就选用复制算法，只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率较高，没有额外的空间对它进行分配担保，就必须使用”标记-清理“和”标记-整理“算法来进行回收。
在Java语言中，可作为GCRoots的对象包括下面几种：
从可达性分析中从GCRoots节点找引用链这个操作为例，可作为GCRoots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中，现在很多应用仅仅方法区就有数百兆，如果要逐个检查里面的引用，必然消耗很多时间。
可达性分析对执行时间的敏感还体现在GC停顿上，因为这项分析工作必须在一个能确保一致性的快照中进行–这里”一致性“的意思是指整个分析期间整个执行系统看起来就像被冻结在某个时间点，不可以出现分析过程中对象引用关系还在不断变化的情况，该点不满足的话分析结果准确性就无法得到保证。这点是导致GC进行时必须停顿所有Java执行线程(Sun公司将这件事情称为”StopTheWorld“)的一个重要原因，即使是在号称(几乎)不会发生停顿的CMS收集器中，枚举根节点时也必须停顿的。
单线程，一个CPU或一条收集线程去完成垃圾收集工作，收集时必须暂停其他所有的工作线程，直到它结束。
虽然如此，它依然是虚拟机运行在Client模式下的默认新生代收集器。简单而高效。
ParNew是Serial收集器的多线程版本。Server模式下默认新生代收集器，除了Serial收集器之外，只有它能与CMS收集器配合工作。
指多条垃圾收集线程并行工作，但此时用户线程仍然处于等待状态。
指用户线程与垃圾收集线程同时执行(但不一定是并行的，可能会交替执行)，用户程序再继续运行，而垃圾收集程序运行于另一个CPU上。
ParallelScavenge收集器是一个新生代收集器，它也是使用复制算法的收集器。看上去来ParNew一样，有什么特别？
ParallelScavenge收集器的特点是它的关注点与其他收集器不同，CMS等收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间。而ParallelScavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间和CPU总小号时间的比值，即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)，虚拟机总共运行了100min，其中垃圾收集花费了1min，那吞吐量就是99%.
停顿时间越短就越适合需要与用户交互的程序，良好的响应速度能提升用户体验，而高吞吐量则可以高效地利用CPU时间，主要适合在后台运算而不需要太多交互的任务。
SerialOld是Serial收集器的老年代版本，它同样是一个单线程收集器。给Client模式下的虚拟机使用。
新生代采用复制算法，暂停所有用户线程；
老年代采用标记-整理算法，暂停所有用户线程；
CMS(ConcurrentMarkSweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上，这类尤其重视服务的响应速度，希望系统停顿时间最短。CMS收集器就非常符合这类应用的需求。
CMS基于标记-清除算法实现。整个过程分为4个步骤：
初始标记，重新标记这两个步骤仍然需要StopTheWorld,初始标记仅仅标记以下GCRoots能直接关联的对象，速度很快。
并发标记就是进行GCRootsTracing的过程；
而重新标记阶段则是为了修正并发标记期间因为用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。这个阶段停顿比初始标记稍微长，但远比并发标记的时间短。
整个过程耗时最长的并发标记和并发清除过程，收集器都可以与用户线程一起工作。总体上来说，CMS收集器的内存回收过程与用户线程一起并发执行的。
1.CMS收集器对CPU资源非常敏感。默认启动的回收线程数是(CPU+3)/4.当CPU4个以上时，并发回收垃圾收集线程不少于25%的CPU资源。
由于CMS运行时还需要给用户空间继续运行，则不能等老年代几乎被填满再进行收集，需要预留一部分空间提供并发收集时，用户程序运行。JDK1.6中，CMS启动阈值为92%.若预留内存不够用户使用，则出现一次ConcurentModeFailure失败。这时虚拟机启动后备预案，临时启用SerialOld收集老年代，这样停顿时间很长。
3.CMS基于”标记-清除“算法实现的，则会产生大量空间碎片，空间碎片过多时，没有连续空间分配给大对象，不得不提前触发一次FUllGC。当然可以开启-XX:+UseCMSCompactAtFullCollection(默认开)，在CMS顶不住要FullGC时开启内存碎片合并整理过程。内存整理过程是无法并发的，空间碎片问题没了，但停顿时间变长。
首先，回答两次，初始标记和重新标记需要。
然后，CMS并发的代价是预留空间给用户，预留不足的时候触发FUllGC，这时SerailOld会STW.
然后，CMS是标记-清除算法，导致空间碎片，则没有连续空间分配大对象时，FUllGC,而FUllGC会开始碎片整理，STW.
即2次或多次。
除直接调用System.gc外，触发FullGC执行的情况有如下四种。
旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象，当执行FullGC后空间仍然不足，则抛出如下错误：
为避免以上两种状况引起的FullGC，调优时应尽量做到让对象在MinorGC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
PermanetGeneration中存放的为一些class的信息等，当系统中要加载的类、反射的类和调用的方法较多时，PermanetGeneration可能会被占满，在未配置为采用CMSGC的情况下会执行FullGC。如果经过FullGC仍然回收不了，那么JVM会抛出如下错误信息：
为避免PermGen占满造成FullGC现象，可采用的方法为增大PermGen空间或转为使用CMSGC。
对于采用CMS进行旧生代GC的程序而言，尤其要注意GC日志中是否有promotionfailed和concurrentmodefailure两种状况，当这两种状况出现时可能会触发FullGC。
promotionfailed是在进行MinorGC时，survivorspace放不下、对象只能放入旧生代，而此时旧生代也放不下造成的；concurrentmodefailure是在执行CMSGC的过程中同时有对象要放入旧生代，而此时旧生代空间不足造成的。
这是一个较为复杂的触发情况，Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象，在进行MinorGC时，做了一个判断，如果之前统计所得到的MinorGC晋升到旧生代的平均大小大于旧生代的剩余空间，那么就直接触发FullGC。
例如程序第一次触发MinorGC后，有6MB的对象晋升到旧生代，那么当下一次MinorGC发生时，首先检查旧生代的剩余空间是否大于6MB，如果小于6MB，则执行FullGC。
当新生代采用PSGC时，方式稍有不同，PSGC是在MinorGC后也会检查，例如上面的例子中第一次MinorGC后，PSGC会检查此时旧生代的剩余空间是否大于6MB，如小于，则触发对旧生代的回收。
除了以上4种状况外，对于使用RMI来进行RPC或管理的SunJDK应用而言，默认情况下会一小时执行一次FullGC。可通过在启动时通过-java-Dsun.rmi.dgc.client.gcInterval=3600000来设置FullGC执行的间隔时间或通过-XX:+DisableExplicitGC来禁止RMI调用System.gc。
首先，在了解G1之前，我们需要清楚的知道，垃圾回收是什么？简单的说垃圾回收就是回收内存中不再使用的对象。
回收的步骤有2步：
那么问题来了，如何判断哪些对象不再被使用呢？我们也有2个方法：
引用计数法就是如果一个对象没有被任何引用指向，则可视之为垃圾。这种方法的缺点就是不能检测到环的存在。
根搜索算法的基本思路就是通过一系列名为”GCRoots”的对象作为起始点，从这些节点开始向下搜索，搜索所走过的路径称为引用链(ReferenceChain)，当一个对象到GCRoots没有任何引用链相连时，则证明此对象是不可用的。
现在我们已经知道如何找出垃圾对象了，如何把这些对象清理掉呢？
常见的方式有复制或者直接清理，但是直接清理会存在内存碎片，于是就会产生了清理再压缩的方式。
总得来说就产生了三种类型的回收算法。
由于对象的存活时间有长有短，所以对于存活时间长的对象，减少被gc的次数可以避免不必要的开销。这样我们就把内存分成新生代和老年代，新生代存放刚创建的和存活时间比较短的对象，老年代存放存活时间比较长的对象。这样每次仅仅清理年轻代，老年代仅在必要时时再做清理可以极大的提高GC效率，节省GC时间。
在jdk1.3.1之前，java虚拟机仅仅能使用Serial收集器。Serial收集器是一个单线程的收集器，但它的“单线程”的意义并不仅仅是说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作，更重要的是在它进行垃圾收集时，必须暂停其他所有的工作线程，直到它收集结束。
Parallel收集器也称吞吐量收集器，相比Serial收集器，Parallel最主要的优势在于使用多线程去完成垃圾清理工作，这样可以充分利用多核的特性，大幅降低gc时间。
CMS收集器在MinorGC时会暂停所有的应用线程，并以多线程的方式进行垃圾回收。在FullGC时不再暂停应用线程，而是使用若干个后台线程定期的对老年代空间进行扫描，及时回收其中不再使用的对象。
G1收集器（或者垃圾优先收集器）的设计初衷是为了尽量缩短处理超大堆（大于4GB）时产生的停顿。相对于CMS的优势而言是内存碎片的产生率大大降低。
其中-XX:+UseG1GC为开启G1垃圾收集器，-Xmx32g设计堆内存的最大内存为32G，-XX:MaxGCPauseMillis=200设置GC的最大暂停时间为200ms。如果我们需要调优，在内存大小一定的情况下，我们只需要修改最大暂停时间即可。
其次，G1将新生代，老年代的物理空间划分取消了。
这样我们再也不用单独的空间对每个代进行设置了，不用担心每个代内存是否足够。
取而代之的是，G1算法将堆划分为若干个区域（Region），它仍然属于分代收集器。不过，这些区域的一部分包含新生代，新生代的垃圾收集依然采用暂停所有应用线程的方式，将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域，G1收集器通过将对象从一个区域复制到另外一个区域，完成了清理工作。这就意味着，在正常的处理过程中，G1完成了堆的压缩（至少是部分堆的压缩），这样也就不会有cms内存碎片问题的存在了。
在G1中，还有一种特殊的区域，叫Humongous区域。如果一个对象占用的空间超过了分区容量50%以上，G1收集器就认为这是一个巨型对象。这些巨型对象，默认直接会被分配在年老代，但是如果它是一个短期存在的巨型对象，就会对垃圾收集器造成负面影响。为了解决这个问题，G1划分了一个Humongous区，它用来专门存放巨型对象。如果一个H区装不下一个巨型对象，那么G1会寻找连续的H分区来存储。为了能找到连续的H区，有时候不得不启动FullGC。
PS：在java8中，持久代也移动到了普通的堆内存空间中，改为元空间。
说起大对象的分配，我们不得不谈谈对象的分配策略。它分为3个阶段：
TLAB为线程本地分配缓冲区，它的目的为了使对象尽可能快的分配出来。如果对象在一个共享的空间中分配，我们需要采用一些同步机制来管理这些空间内的空闲空间指针。在Eden空间中，每一个线程都有一个固定的分区用于分配对象，即一个TLAB。分配对象时，线程之间不再需要进行任何的同步。
对TLAB空间中无法分配的对象，JVM会尝试在Eden空间中进行分配。如果Eden空间无法容纳该对象，就只能在老年代中进行分配空间。
最后，G1提供了两种GC模式，YoungGC和MixedGC，两种都是StopTheWorld(STW)的。下面我们将分别介绍一下这2种模式。
YoungGC主要是对Eden区进行GC，它在Eden空间耗尽时会被触发。在这种情况下，Eden空间的数据移动到Survivor空间中，如果Survivor空间不够，Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中，也有部分数据晋升到老年代空间中。最终Eden空间的数据为空，GC停止工作，应用线程继续执行。
这时，我们需要考虑一个问题，如果仅仅GC新生代对象，我们如何找到所有的根对象呢？老年代的所有对象都是根么？那这样扫描下来会耗费大量的时间。于是，G1引进了RSet的概念。它的全称是RememberedSet，作用是跟踪指向某个heap区内的对象引用。
在CMS中，也有RSet的概念，在老年代中有一块区域用来记录指向新生代的引用。这是一种point-out，在进行YoungGC时，扫描根时，仅仅需要扫描这一块区域，而不需要扫描整个老年代。
但在G1中，并没有使用point-out，这是由于一个分区太小，分区数量太多，如果是用point-out的话，会造成大量的扫描浪费，有些根本不需要GC的分区引用也扫描了。于是G1中使用point-in来解决。point-in的意思是哪些分区引用了当前分区中的对象。这样，仅仅将这些对象当做根来扫描就避免了无效的扫描。由于新生代有多个，那么我们需要在新生代之间记录引用吗？这是不必要的，原因在于每次GC时，所有新生代都会被扫描，所以只需要记录老年代到新生代之间的引用即可。
需要注意的是，如果引用的对象很多，赋值器需要对每个引用做处理，赋值器开销会很大，为了解决赋值器开销这个问题，在G1中又引入了另外一个概念，卡表（CardTable）。一个CardTable将一个分区在逻辑上划分为固定大小的连续区域，每个区域称之为卡。卡通常较小，介于128到512字节之间。CardTable通常为字节数组，由Card的索引（即数组下标）来标识每个分区的空间地址。默认情况下，每个卡都未被引用。当一个地址空间被引用时，这个地址空间对应的数组索引的值被标记为”0″，即标记为脏被引用，此外RSet也将这个数组下标记录下来。一般情况下，这个RSet其实是一个HashTable，Key是别的Region的起始地址，Value是一个集合，里面的元素是CardTable的Index。
MixGC不仅进行正常的新生代垃圾收集，同时也回收部分后台扫描线程标记的老年代分区。
在进行MixGC之前，会先进行globalconcurrentmarking（全局并发标记）。globalconcurrentmarking的执行过程是怎样的呢？
在G1GC中，它主要是为MixedGC提供标记服务的，并不是一次GC过程的一个必须环节。globalconcurrentmarking的执行过程分为五个步骤：
在此阶段，G1GC对根进行标记。该阶段与常规的(STW)年轻代垃圾回收密切相关。
G1GC在初始标记的存活区扫描对老年代的引用，并标记被引用的对象。该阶段与应用程序（非STW）同时运行，并且只有完成该阶段后，才能开始下一次STW年轻代垃圾回收。
该阶段是STW回收，帮助完成标记周期。G1GC清空SATB缓冲区，跟踪未被访问的存活对象，并执行引用处理。
在这个最后阶段，G1GC执行统计和RSet净化的STW操作。在统计期间，G1GC会识别完全空闲的区域和可供进行混合垃圾回收的区域。清理阶段在将空白区域重置并返回到空闲列表时为部分并发。
提到并发标记，我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有用的方法，利用它可以推演回收器的正确性。首先，我们将对象分成三种类型的。
当GC开始扫描对象时，按照如下图步骤进行对象的扫描：
根对象被置为黑色，子对象被置为灰色。
继续由灰色遍历,将已扫描了子对象的对象置为黑色。
遍历了所有可达的对象后，所有可达的对象都变成了黑色。不可达的对象即为白色，需要被清理。
我们看下面一种情况，当垃圾收集器扫描到下面情况时:
这时候应用程序执行了以下操作：
这样，对象的状态图变成如下情形：
这时候垃圾收集器再标记扫描的时候就会下图成这样：
很显然，此时C是白色，被认为是垃圾需要清理掉，显然这是不合理的。那么我们如何保证应用程序在运行的时候，GC标记的对象不丢失呢？有如下2中可行的方式：
刚好这对应CMS和G1的2种不同实现方式：
在CMS采用的是增量更新（Incrementalupdate），只要在写屏障（writebarrier）里发现要有一个白对象的引用被赋值到一个黑对象的字段里，那就把这个白对象变成灰色的。即插入的时候记录下来。
在G1中，使用的是STAB（snapshot-at-the-beginning）的方式，删除的时候记录所有的对象，它有3个步骤：
2.在并发标记的时候所有被改变的对象入队（在writebarrier里把所有旧的引用所指向的对象都变成非白的）
这样，G1到现在可以知道哪些老的分区可回收垃圾最多。当全局并发标记完成后，在某个时刻，就开始了MixGC。这些垃圾回收被称作“混合式”是因为他们不仅仅进行正常的新生代垃圾收集，同时也回收部分后台扫描线程标记的分区。混合式垃圾收集如下图：
混合式GC也是采用的复制的清理策略，当GC完成后，会重新释放空间。
前面介绍过使用GC的最基本的参数：
前面2个参数都好理解，后面这个MaxGCPauseMillis参数该怎么配置呢？这个参数从字面的意思上看，就是允许的GC最大的暂停时间。G1尽量确保每次GC暂停的时间都在设置的MaxGCPauseMillis范围内。那G1是如何做到最大暂停时间的呢？这涉及到另一个概念，CSet(collectionset)。它的意思是在一次垃圾收集器中被收集的区域集合。
YoungGC：选定所有新生代里的region。通过控制新生代的region个数来控制youngGC的开销。
在理解了这些后，我们再设置最大暂停时间就好办了。首先，我们能容忍的最大暂停时间是有一个限度的，我们需要在这个限度范围内设置。但是应该设置的值是多少呢？我们需要在吞吐量跟MaxGCPauseMillis之间做一个平衡。如果MaxGCPauseMillis设置的过小，那么GC就会频繁，吞吐量就会下降。如果MaxGCPauseMillis设置的过大，应用程序暂停时间就会变长。G1的默认暂停时间是200毫秒，我们可以从这里入手，调整合适的时间。
设置的G1区域的大小。值是2的幂，范围是1MB到32MB之间。目标是根据最小的Java堆大小划分出约2048个区域。
设置STW工作线程数的值。将n的值设置为逻辑处理器的数量。n的值与逻辑处理器的数量相同，最多为8。
如果逻辑处理器不止八个，则将n的值设置为逻辑处理器数的5/8左右。这适用于大多数情况，除非是较大的SPARC系统，其中n的值可以是逻辑处理器数的5/16左右。
设置并行标记的线程数。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右。
避免使用以下参数：
避免使用-Xmn选项或-XX:NewRatio等其他相关选项显式设置年轻代大小。固定年轻代的大小会覆盖暂停时间目标。
在某些情况下，G1触发了FullGC，这时G1会退化使用Serial收集器来完成垃圾的清理工作，它仅仅使用单线程来完成GC工作，GC暂停时间将达到秒级别的。整个应用处于假死状态，不能处理任何请求，我们的程序当然不希望看到这些。那么发生FullGC的情况有哪些呢？
G1启动标记周期，但在MixGC之前，老年代就被填满，这时候G1会放弃标记周期。这种情形下，需要增加堆大小，或者调整周期（例如增加线程数-XX:ConcGCThreads等）。
G1在进行GC的时候没有足够的内存供存活对象或晋升对象使用，由此触发了FullGC。可以在日志中看到(to-spaceexhausted)或者（to-spaceoverflow）。解决这种问题的方式是：
c.也可以通过增加-XX:ConcGCThreads选项的值来增加并行标记线程的数目。
当巨型对象找不到合适的空间进行分配时，就会启动FullGC，来释放空间。这种情况下，应该避免分配大量的巨型对象，增加内存或者增大-XX:G1HeapRegionSize，使巨型对象不再是巨型对象。