在Java中垃圾回收的目的是回收释放不再被引用的实例对象,这样做可以减少内存泄漏、内存溢出问题的出现。
什么情况下会触发Minor GC:在新生代的Eedn区满了会触发。
什么情况下会触发Full GC:
Full GC发生过于频繁,会影响性能,因为Full GC会导致STW(Stop-The-World),STW指的是用户线程在运行至安全点(safe point)或安全区域(safe region)之后,就自行挂起,进入暂停状态,对外的表现就是卡顿。所以应尽量减少Full GC的次数。不过不论是minor gc还是major gc都会STW,区别只在于STW的时间长短。
Java的自动内存管理主要解决了给对象分配内存和回收分配给对象的内存两个问题,先来看下Java虚拟机是如何为对象分配内存的。
Java对象的内存分配主要就是在堆上,Java堆的基本结构如下,大体上可以分为新生代和老年代。

新生代默认占1/3,老年代默认占2/3,新生代包含Eden区、From Survivor0区和 To Survivor1区,默认比例是8:1:1,老年代就一个Old Memory区。
一般情况下是这样分配的
每次移动,对象的年龄就会加1,当年龄到达15时(默认是15,对象晋升老年代的年龄阈值可以通过参数 -XX: MaxTenuringThreshold 设置),会从新生代进入老年代。
下面介绍几种收集器的内存分配策略
注:为了可以更好地适应不同程度的内存状况,虚拟机并不是必须要求对象的年龄达到MaxTenuringThreshold才进入老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold
最后说下空间分配担保机制,在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果大于,则表明Minor GC可以安全进行。如果不大于,虚拟机会查看HandlePromotionFailure设置是否允许担保失败。如果允许,则会继续检查老年代的最大可利用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则会尝试进行一次Minor GC(存在一定风险),如果小于或者HandlePromotionFailure设置不允许担保失败,则这一次会进行Full GC。
这里解释为什么会存在风险,因为在新生代使用的垃圾收集算法是复制算法,前面也提到了,只有一个Survivor空间作为轮换备份,如果这时出现大量对象在Minor GC后仍然存活,则需要老年代进行担保,Survivor无法容纳的对象会直接进入老年代,风险就是Survivor无法容纳的对象有多大很难确定,也就无法保证老年代的空间一定够用,一般是取之前每一次回收晋升到老年代对象的平均大小作为参考值。
说完了空间分配担保机制的概念,不知道大家看出来这玩意儿有什么用了吗?
其实很简单,就是怕Minor GC后需要进入到老年代的对象太多了,老年代没有那么大空间,先提前检查一下,如果检查结果显示老年代确实装不下,那么这次Minor GC就得改成Full GC,那Full GC完了老年代空间还是不够呢?那会OOM内存溢出的。
在JDK1.2之后,Java对引用的概念进行了扩充,主要分为强引用、软引用、弱引用、虚引用
判断对象是否死亡的常见方法主要有引用计数法和可达性分析法两种
给对象添加一个引用计数器,每当有一个地方引用它时,计数器就会加1;当引用失效时,计数器就减1,当计数器为0就是没有被使用的对象,但主流的Java虚拟机并没有选择用引用计数法来管理内存,因为无法解决对象之间相互循环引用的问题,就是两个对象相互引用,除此之外,两个对象并没有其他引用,这两个对象已经不可能被访问了,但他们的引用计数都不为0,所以无法被垃圾收集器回收。
可达性分析法就是通过一系列被称为”GC Roots“的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径被称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明该对象是不可用的,也就是可回收的,如下图,对象object 5、object 6、object 7虽然有关联,但他们到GC Roots是不可达的,所以也会判定是可以回收的,这样解决了对象之间相互引用导致不能回收的问题。

注:在Java语言中,可以作为GC Roots的对象主要有以下几种:
首先,永久代这个概念是HotSpot虚拟机中独有的,其他Java虚拟机中并没有永久代的概念。
在JDK1.8之前JVM存在永久代,在JDK1.8被元空间替代。那什么是永久代呢?永久代和元空间都是方法区的具体实现,方法区只是一种规范。
在永久代中主要是存放类的信息(成员方法、构造器、类加载器等)及运行时常量池,所以当永久代满了也会进行回收。
在永久代发生的内存回收主要是常量池的回收和类型的卸载。常量池的回收相对容易,只要常量池中的常量没有被任何地方引用,就可以被回收。判断一个类型是否可以回收比较麻烦,主要看以下几个方面:
为什么在JDK1.8会使用元空间取代永久代?
永久代使用的是设定好的虚拟机内存,无法动态扩展内存空间,当加载的类过多就可能发生OOM,并且永久代的内存大小设置也是难以确定的,所以对永久代调优也是比较困难的。
元空间的出现就解决了永久代的问题,因为元空间不再使用虚拟机的内存了,而是使用了本地内存,本地内存可以自动扩展调节,内存不足也不会触发Full GC。
可以通过调用 system.gc() 方法通知虚拟机进行垃圾回收,但Java虚拟机规范并不能保证一定会执行。
垃圾收集算法主要有标记-清除算法、标记-复制算法、标记-整理算法、分代收集算法
标记-清除算法主要包含标记和清除两个阶段,首先标记出所有需要回收的对象,在标记完成后同一回收所有被标记的对象。

标记-清除算法有两个明显的缺点:第一就是效率低,标记和清除两个过程的效率都不高;第二是空间问题,标记清除后会产生大量不连续内存碎片,空间碎片太多会导致以后在程序运作过程中需要分配大对象时,无法找到足够的连续内存进而提前触发另一次垃圾收集动作。
为了解决标记-清除算法的效率问题,标记-复制算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这块的内存用完了,就将活着的对象复制到另一块上面,再将已使用过的内存一次清理掉,如下图

标记-复制算法的好处显而易见,每次都是对半个区进行内存回收,内存分配时也不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效,缺点也是显而易见的,每次可以使用的内存只有原来的一半。
如果对象存活率比较高时使用标记-复制算法就要进行比较多的复制操作,效率会变低,针对这种场景,提出了一种标记-整理算法,和标记-清除算法不同的是,标记完后不直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉其他地方的内存,如下图

按照前面讲的,将Java堆分为新生代和老年代,根据每个年代的特点采用合适的收集算法。例如,在新生代每次垃圾回收时会有大量对象死亡,只有少量存活,所以在新生代选择用标记-复制算法,在老年代每次垃圾回收会有大量对象存活,考虑使用标记-清除或标记-整理算法。在商业虚拟机中一般都是采用分代收集算法。
垃圾回收算法是内存回收的方法论,垃圾收集器则是内存回收的具体实现。Java规范中并没有对垃圾收集器的实现有任何规范,所以不用的厂商、不同的版本的虚拟机提供的垃圾收集器是不同的,这里主要讨论的是HotSpot虚拟机所包含的虚拟机,按照年代划分如下:

其中新生代收集器有Serial、ParNew、Parallel,老年代收集器有CMS、Serial Old、Parallel Ol,G1则既可以在新生代收集,又能在老年代收集。两个垃圾收集器之间如果存在连线,则说明它们可以搭配使用。
那哪个收集器的性能最好呢,其实这里并不存在最好的收集器,只有在对应场景中最合适的垃圾收集器。
Serial收集器是最基本的收集器,并且是单线程的收集器,这里的单线程不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,在它进行垃圾收集时,必须暂停其他所有的线程工作,直到它收集结束。不难想象,这对很多应用来说都是难以接受的,如下图

除了上面写到的缺点,Serial收集器也有着优于其他收集器的地方,简单而高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾回收自然可以获得最高的单线程的收集效率。
ParNew收集器是Serial收集器的多线程版本,除了使用多条线程进行垃圾回收外,其他地方与Serial一样,从下图中也可以看出,除了多了几个GC线程,和Serial收集器并没有什么区别

Parallel Scavenge 是一个使用标记-复制算法的多线程收集器,看起来和ParNew很像,Parallel Scavenge收集器的关注点和与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间(用户体验),而Parallel Scavenge收集器的关注点是达到一个可控制的吞吐量(提高CPU的效率),这里的吞吐量指的是CPU用于运行代码的时间和CPU总消耗时间的比值。
那更短的停顿时间和更高的吞吐量有什么好处呢?
停顿时间越短越适合需要与用户交互的程序,良好的响应速度可以提升用户体验。更高的吞吐量适合在后台运算而不需要太多交互的程序,高吞吐量可以提高CPU的利用率,尽快地完成程序的运算任务。
Serial Old是Serial收集器的老年代版本,同样是单线程收集器,采用标记-整理算法,主要有两大用途:一是在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用,二是作为CMS收集器的后备预案。
Parallel Old是Parallel Scavenge收集器的老年代版本,采用多线程和标记-整理算法。该收集器是在JDK1.6才开始提供的,因为当新生代选择了Parallel Scavenge收集器,老年代只能选择Serial Old(Parallel Scavenge无法与CMS搭配使用),这时Serial Old收集器会影响整体的吞吐量,所以提供了Parallel Old收集器和Parallel Scavenge搭配使用。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,采用标记-清除算法,其运作过程可以分为初始标记、并发标记、重新标记、并发清除四个步骤。
上述四个步骤中,初始标记和重新标记两个步骤会“Stop The Word”,也就是会暂停用户线程,如下图

这里解释下在垃圾收集器的语境中,并行和并发的概念:
CMS的优点是并发收集,停顿时间短,缺点主要有以下三个:
G1收集器是面向服务端应用的垃圾收集器,回收范围包括新生代和老年代,主要有以下特点:
G1收集器的运作步骤如下:
看起来和CMS很像,如下图
