理解Java虚拟机垃圾回收机制的底层原理，是系统调优与线上问题排查的基础，也是一个高级Java程序员的基本功，本文就针对Java垃圾回收这一主题做一些整理与记录。Java垃圾回收器的种类繁多，它们的设计要在吞吐量（内存空间）与实时性（用户线程中断）方面进行权衡，各个垃圾回收器的适应场景也不尽相同（如：桌面应用，web应用），因此，这里我们只讨论JDK8下的默认垃圾回收器，毕竟目前JDK8版本是业界的主流（占80%），并且我们只讨论堆内存空间的垃圾回收。
二、如何判断对象是否可回收？
首先思考一个问题，内存堆中那么多对象，回收器要回收哪些对象？怎么判断出这些要回收的对象呢？因此对于垃圾回收，判断并标识对象是否可回收是第一步。从理论层面来说，判断对象是否可回收一般两种方法。
第一种、引用计数器算法：每当对象被引用一次计数器加1，对象失去引用计数器减1，计数器为0是就可以判断对象死亡了。这种算法简单高效，但是对于循环引用或其他复杂情况，需要更多额外的开销，因此Java几乎不使用该算法。
第二种、跟搜索算法-可达性分析算法：所谓可达性分析是指，顺着GCRoots根一直向下搜索（用一个成语概括就是“顺藤摸瓜”），整个搜索的过程就构成了一条“引用链”，只要在引用链上的对象叫做可达，在引用链之外的（说明跟GCRoots没有任何关系）叫不可达，不可达的对象就可以判断为可回收的对象。哪些对象可作为GCRoots对象呢？包括如下：
如果对虚拟机的内存布局与运行流程有所了解的话，这些作为GCRoots都很好理解，它们是程序运行时的源头，程序的正常运行必须依赖它们，而与这些源头没有任何关系的对象，即可视为可回收对象。就好比“瓜从藤上掉下来了，那这瓜肯定也没有用了”
可达性分析从理论上很好理解，但在垃圾收集器具体运行时，要考虑的问题不知道要复杂多少倍，因为在可达性分析的同时，程序也是在并行运行着，整个内存堆的状态随着程序的运行是实时变化的，要实现分析结果与内存状态的一致性，就必须要暂停用户线程，在一个快照去进行分析。
可达性分析解决了判断对象是否可回收的问题，那么在垃圾回收时内存空间会发生哪些变化呢？这就是垃圾回收算法要讨论的问题，我们根据算法对内存采取的不同操作，可将垃圾回收算法分为3种，标记-清除算法、标记-复制算法、标记-整理算法。
根据名称就可以理解改算法分为两个阶段：首先标记出所有需要被回收的对象，然后对标记的对象进行统一清除，清空对象所占用的内存区域，下图展示了回收前与回收后内存区域的对比，红色的表示可回收对象，橙色表示不可回收对象，白色表示内存空白区域。
第一个：是执行效率不可控，试想一下如果堆中大部分的对象都可回收的，收集器要执行大量的标记、收集操作。第二个：产生了许多内存碎片，通过回收后的内存状态图可以知道，被回收后的区域内存并不是连续的，当有大对象要分配而找不到满足大小的空间时，要触发下一次垃圾收集。
针对标记-清除算法执行效率与内存碎片的缺点，计算机科学家又提出了一种“半复制区域”的算法。
标记-复制算法将内存分为大小相同的两个区域，运行区域，预留区域，所有创建的新对象都分配到运行区域，当运行区域内存不够时，将运作区域中存活对象全部复制到预留区域，然后再清空整个运行区域内存，这时两块区域的角色也发生了变化，每次存活的对象就像皮球一下在运行区域与预留区域踢来踢出，而垃圾对象会随着整个区域内存的清空而释放掉，内存前后的状态参考下图：
标记-复制算法在大量垃圾对象的情况下，只需复制少量的存活对象，并且不会产生内存碎片问题，新内存的分配只需要移动堆顶指针顺序分配即可，很好的兼顾了效率与内存碎片的问题。
预留一半的内存区域未免有些浪费了，并且如果内存中大量的是存活状态，只有少量的垃圾对象，收集器要执行更多次的复制操作才能释放少量的内存空间，得不偿失。
标记-复制算法要浪费一半内存空间，且在大多数状态为存活状态时使用效率会很低，针对这一情况计算机科学家又提出了一种新的算法“标记-整理算法”，标记整理算法的标记阶段与其他算法一样，但是在整理阶段，算法将存活的对象向内存空间的一端移动，然后将存活对象边界以外的空间全部清空，如下图所示：
Java堆内存空间新生代、老年代是如何划分的？对象创建后是如何分配到不同的区域的？结合下图可以知道，整个堆内存被分为了2个大的区域，新生代，老年代，默认情况下新生代占1/3的空间，老年代占2/3的空间，新生代又分为两个区Eden区Survial区，Survial又分为S0、S1区默认各占8/10与1/10，1/10的空间。
为什么要这么设计呢？为什么要分那么多不同的内存区域干嘛？这是由对象的生命周期特征、与各类垃圾回收算法的优缺点所决定的，这正式垃圾回收器设计的理论基础。经过统计分析，大多数应用程序对象生命周期符合两个特征：
绝大多数的对象都是“朝生夕灭”的，既创建不久即可消亡;熬过越多此垃圾回收过程的对象就越难以消亡;
一块独立的内存区域只能使用一种回收算法，根据对象生命周期特征，将其划分到不同的区域，再对特定区域使用特定的垃圾回收算法，只有这样才能将垃圾算法的优点发挥到极致，这种组合的垃圾回收算法叫：分代垃圾算法。。比如：在新生代使用标记-复制算法，在老年代使用标记-整理算法。
我们分析了如何判断对象是否可回收，还有3中基础的垃圾回收算法，以及年轻代、老年代的内存区域划分与原因。接下来我们就一步一步来分析堆内存的回收流程。
假设在第一垃圾回收之前，内存中的状态如图所示，Eden区有2个存活对象，3个垃圾对象，内存的可用区域已经所剩无几，Survivor区因为还没有进行任何MinorGC所以是空的，有1个大对象直接分配到了老年代，
当新的对象分配到Eden区，发现内存空间不够，于是触发第一次MinorGC，垃圾回收器首先将Edne区中的两个存活对象复制到S0区，然后在清空Eden区的空间，如下图：
经过第1次MinorGC程序再运行一段时间后，堆内存状态如下：Eden区又产生了大量的对象，并且大部分对象都可回收状态，这也符合对象“朝生夕灭”的特征，S0区中也有1个对象可以回收，S1与老年代没有变化，在这种状态下，如果新对象分配再次触发MinorGC会发生什么呢？
新对象分配Eden区空间不足，又触发了第二次MinorGC，第二次MinorGC与第一次GC时在Eden区的操作是一样的：将Eden区存活的对象复制到S1区，然后在清空整个Eden区，同时也将S0区存活的对象复制到S1区并将对象的年龄加1，再清空S0区，GC后的状态如下图所示：
经过第二MinorGC后程序又运行了一段时间，Eden区中有生成了很多对象，S1区也有一个对象可回收。
在接下来的每次MinorGC时，都是第二次一样，从Eden区和survivor非空白区移动存活对象到survivor区中空白区域，并清空这两个区域内存空间，存活对象每此从survivor两个区域移动一次，对象年龄加1，下图表示经过了15次MinorGC后的堆内存状态。
对于年轻代区域的内存收集，使用的是标记-复制算法，只是为了减少复制算法空白区域的内存浪费，并不是将内存一份为二，而是巧妙的将内存分为三个区域，预留的空白区域只占整个年轻代区域的1/10。
以上是年轻代的分配与回收问题，那对象如何进入老年代呢？个人认为对象进入老年代，可以分为2种类型6种情况。
第一种类型–直接分配：对象创建时直接分配到老年代具体分为3种情况。
新生代分配担保，在执行MinorGC时要将Eden区存活的对象复制到Survivor区，但是Survivor区默认空间是只有新生代的2/10，实际使用的只有1/10，当Survivor区内存不够所有存活对象分配时，就需要将Survivor无法容纳的对象分配到老年代去，这种机制就叫分配担保。
内存分配担保机制在执行MinorGC时要将Eden区存活的对象复制到Survivor区，但是Survivor区默认空间是只有新生代的2/10，实际使用的只有1/10，当Survivor区内存不够所有存活对象分配时，就需要Survivor无法容纳将对象分配到老年代空间中，这种机制就叫分配担保，但是，老年代的空间也是有限的，如果老年代中空间也不够的话，那只能乖乖的执行一次FullGC了。
当有对象要进入老年代，而老年代空间又不足时就会触发FullGC，当然，反过来说触发FullGC的条件不仅仅只是老年代空间不足，FullGC使用的算法是上面说的标记-整理算法。
Java能用于机器学习和数据科学吗？
如何高效保证强化学习过程中的安全性？
如何设计区块链应用程序架构？
我是如何在7分钟内黑掉40家网站的？