咱们都知道java语言与C语言最大的区别就是内存自动回收,那么JVM是怎么控制内存回收的,这篇文章将介绍JVM垃圾回收的几种算法,从而了解内存回收的基本原理。java
stop the world算法
在介绍垃圾回收算法以前,咱们须要先了解一个词“stop the world”,stop the world会在执行某一个垃圾回收算法的时候产生,JVM为了执行垃圾回收,会暂时java应用程序的执行,等垃圾回收完成后,再继续运行。若是你使用JMeter测试过java程序,你可能会发如今测试过程当中,java程序有不规则的停顿现象,其实这就是“stop the world”,停顿的时候JVM是在作垃圾回收。因此尽量减小stop the world的时间,就是咱们优化JVM的主要目标。接下来咱们看一下目前有哪些常见垃圾回收的算法。并发
引用计数法性能
引用计数法顾名思义,就是对一个对象被引用的次数进行计数,当增长一个引用计数就加1,减小一个引用计数就减1。测试
上图表示3个Teacher的引用指向堆中的Teacher对象,那么Teacher对象的引用计数就是3,以此类推Student对象的引用计数就是2。优化
上图表示Teacher对象的引用减小为2,Student对象的引用减小为0(减小的缘由是该引用指向了null,例如teacher3=null),按照引用计数算法,Student对象的内存空间将被回收掉。spa
引用计数算法原理很是简单,是最原始的回收算法,可是java中没有使用这种算法,缘由有2。1是频繁的计数影响性能,2是它没法处理循环引用的问题。对象
例如Teacher对象中引用了Student对象,Student对象中又引用了Teacher对象,这种状况下,对象将永远没法被回收。blog
标记清除内存
标记清除算法,它是不少垃圾回收算法的基础,简单来讲有两个步骤:标记、清除。
标记:遍历全部的GC Roots,并将从GC Roots可达的对象设置为存活对象;
清除:遍历堆中的全部对象,将没有被标记可达的对象清除;
注意上图灰色的对象,由于从GC Root遍历不到它们(尽管它们自己有引用关系,但从GC Root没法遍历到它们),所以它们没有被标记为存活对象,在清除过程当中将会被回收。
这里须要注意的是标记清除算法执行过程当中,会产生“stop the world”,让java程序暂停等待以保证在标记清除的过程当中,不会有新的对象产生。为何必须暂停java程序呢?举个例子,若是在标记过程完成后,又新产生了一个对象,而该对象已经错过了标记期,那么在接下来的清除流程中,这个新产生的对象由于未被标记,因此将被视为不可达对象而被清除,这样程序就会出错,所以标记清除算法在执行时,java程序将被暂停,产生“stop the world”。
接下来咱们总结一下标记清除算法:
一、由于涉及大量的内存遍历工做,因此执行性能较低,这也会致使“stop the world”时间较长,java程序吞吐量下降;
二、咱们注意到对象被清除以后,被清除的对象留下内存的空缺位置,形成内存不连续,空间浪费。
接下来咱们看一下其余算法能不能改善这些问题?
标记压缩
标记压缩算法你可能已经想到了,它就是在标记清除算法的基础上,增长了压缩过程。
在进行完标记清除以后,对内存空间进行压缩,节省内存空间,解决了标记清除算法内存不连续的问题。
注意标记压缩算法也会产生“stop the world”,不能和java程序并发执行。在压缩过程当中一些对象内存地址会发生改变,java程序只能等待压缩完成后才能继续。
复制算法
复制算法简单来讲就是把内存一分为二,但只使用其中一份,在垃圾回收时,将正在使用的那分内存中存活的对象复制到另外一份空白的内存中,最后将正在使用的内存空间的对象清除,完成垃圾回收。
复制算法相对标记压缩算法来讲更简洁高效,但它的缺点也显而易见,它不适合用于存活对象多的状况,由于那样须要复制的对象不少,复制性能较差,因此复制算法每每用于内存空间中新生代的垃圾回收,由于新生代中存活对象较少,复制成本较低。它另一个缺点是内存空间占用成本高,由于它基于两分内存空间作对象复制,在非垃圾回收的周期内只用到了一分内存空间,内存利用率较低。
小结
以上咱们介绍了常见的垃圾回收算法,这些算法各有各的优缺点,但在JVM中并非单纯的使用特定的算法,而是使用的一种叫垃圾回收器的东西,垃圾回收器能够看作一系列算法的不一样组合,在不一样的场景使用合适的垃圾回收器,才能起到事半功倍的效果。咱们下一篇将介绍垃圾回收器。
参考资料:
《实战Java虚拟机》 葛一鸣
《深刻理解Java虚拟机(第2版)》 周志明