GC
今天,我们来探讨一下JVM里面的GC算法吧,这也是JAVA用户所不能避开的问题。
JAVA相比之C++的一大的区别是,JAVA能够基于JVM对内存进行自动的分配与释放,而不像C++一样需要工程师手动的注意各个变量的内存分配以及释放,时时刻刻得小心是否已经造成了内存泄漏这些问题,比较麻烦。
那么,到底会有哪几种GC算法呢?我们就按照一个时间层层递进的顺序来大致了解一下吧。
引用计数法
引用计数法是最经典最古老的一种垃圾收集方法。
引用计数法的基本思路是:对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就会+1,反之,当引用失效时,引用计数器就-1.只要对象A的引用计数器的值为0,则对象A就不可能在被使用,此时可以被回收了。
实现也是比较简单的,就是为每一个对象创建一个整型的计数器即可。但是,这个方法有个致命缺点,那就是它无法处理循环引用的问题。因此,JAVA的垃圾回收器中,没有采用这种算法。
而所谓的循环引用是,有两个对象A和B,A对象中含有B的引用,B对象含有A的引用,此时,A和B的引用计数器的值都不是0,但是在系统中,又没有第三个对象引用了A或B,此时,A和B应该是被回收的对象,是垃圾对象,但是由于垃圾对象之间相互引用,从而是垃圾回收器无法识别,引发内存泄漏。
标记-清除算法(Mark-Sweep)
既然引用计数法有着缺陷,那么我们就换个思路,给每一个还存活的对象进行标记,而没有标记的就是垃圾对象,这样就很方便于垃圾回收器回收清除垃圾对象了。也就是说,标记-清除算法将垃圾回收分为标记、清除两个阶段。
其基本思路是:在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。然后在清除阶段,清除所有未被标记的对象。
但是,标记-清除算法最大的问题是内存空间碎片化的问题。回收后的内存空间是不连续的。在对象的堆空间分配过程中,尤其是大对象的内存分配,不连续的内存空间的效率要低于连续的空间。
复制算法
复制算法,算是从标记-清除算法中演变而来的一种算法。
首先,我们知道现在JVM中的JAVA堆大体分为三个区,分别是年轻代(YYoung Generation)、年老代(Old Generation),以及持久代(Permanent Generation)。在年轻代中又有一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个)。而我们的复制算法便是使用在这两个Survivor区之间的。
接着,我们假定在这个年轻代中,只有两个Survivor区,每次只使用其中的一个Survivor区。好,那么我们把这两个Survivor区命名为S1和S2。假设这次只使用S1区。
在S1中使用标记-清除算法,对还存活的对象进行标记,然后将被标记的还存活的对象复制到S2中,最后清除S1中所有的对象。这样便做到了防止内存碎片化的情况发生。
那么总结一下,复制算法的基本思路是:将原有的内存空间分为两块,每次只使用其中的一块,在垃圾回收的时候,将正在使用的内存中的存活对象复制到未使用的内存块之中,之后清除正在使用的内存块中的所有对象。然后交换两个内存块的角色,完成垃圾回收。
优点是:
1、高效。在系统中垃圾对象较多的时候,也就是真正需要垃圾回收的时候,复制算法需要复制的存活对象并不多,所以复制算法的效率很高。
2、确保回收后的内存空间没有碎片。
缺点是,需要将系统的内存空间折半。
另外,在垃圾回收的时候,eden空间中的存活对象会被复制到未使用的survivor空间中,正在使用的survivor空间中的年轻对象也会被复制到未使用的survivor中(大对象,或者年老对象会直接进入年老代,如果未使用的survivor空间已满,则对象也会直接进入年老代)。
标记-压缩算法(Mark-Compact)
我们知道,复制算法的高效性是建立在存活对象少、垃圾对象很多的前提下。这种情况在年轻代是经常发生的,但是在年老代,更常见的是大部分对象是存活对象,如果依然使用复制算法的话,由于存活对象较多,复制成本便变得很大。
而标记-压缩算法正是一种年老代的回收算法。
标记-压缩算法是在标记-清除算法上进行改进过后的算法。基本思路是:首先跟标记-清除算法一样,需要从根结点开始,对所有可达的对象进行标记,之后,不是简单的清除,而是将所有的存活对象压缩到内存的一端,接着才是清除边界以外的所有的内存空间。
这样,既避免了内存的碎片化,也不需要两块相同的内存空间。性价比较高。
增量算法
增量算法是专门对垃圾回收时Stop the World状态的一种优化算法。
对于大部分的垃圾回收算法来说,在垃圾回收的过程中,软件会处于一种Stop the World 的状态。在这个状态下,应用程序所有的线程都会挂起,暂停一切正常的工作,等待垃圾回收的完成。如果垃圾回收时间过长,应用程序会被挂起很长时间,将会严重影响用户体验和系统的稳定性。
而增量算法的基本思路是:如果一次性的将所有的垃圾进行处理,需要造成系统长时间的停顿暂停,那么可以让垃圾收集线程和应用程序线程交替执行。这样,每次垃圾回收只收集一小片区域的内存空间,接着又切换到应用程序线程。以此反复,知道垃圾回收完成。这样,便可以减少系统的停顿时间。
缺点是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。
后记
这些便是一些简单的GC算法了。如若有错,还请斧正。多谢。
install_url
to use ShareThis. Please set it in _config.yml
.