咱们把还被引用的对象称为活的,把再也不被引用的对象认为是死的,也就是咱们说的垃圾。GC 的工做就是找到死的对象,释放(也称为回收)这些对象所使用的空间的过程称为垃圾收集。java
咱们把 GC 管理的内存称为 堆(heap),垃圾收集启动的时机取决于各个垃圾收集器,一般,垃圾收集发生于整个堆或堆的部分已经被使用光了,或者使用的空间达到了某个百分比阈值web
对于内存分配,实现的难点在于在堆中找到一块没有被使用的肯定大小的内存空间。因此,对于大部分垃圾回收算法来讲避免内存碎片化是很是重要的,它将使得空间分配更加高效。算法
安全和全面安全
活的对象必定不能被清理掉,死的对象必定不能在几个回收周期结束后还在内存中。多线程
高效并发
不能将咱们的应用程序挂起太长时间。咱们须要在时间、空间、频次上做出权衡。好比,若是堆内存很小,每次垃圾收集就会很快,可是频次会增长。若是堆内存很大,好久才会被填满,可是每一次回收须要的时间很长。oracle
内存碎片限制svg
当对垃圾对象的内存被释放时,空闲空间可能会出如今不一样区域的小块中,这样在任何一个相邻区域中均可能没有足够的空间用于分配一个大型对象。消除分段的一种方法称为压缩,在下面的各类垃圾收集器设计选择中将讨论。性能
可伸缩性线程
可伸缩性也很重要。在多处理器系统中,分配不该该成为多线程应用程序的可伸缩性瓶颈,并且收集也不该该成为瓶颈。
在设计或选择垃圾收集算法时,必须作出一些选择:
并行:多个垃圾回收线程同时工做,互不影响
并发:垃圾回收线程和应用程序线程同时工做,应用程序不须要挂起
串行收集的状况,即便是多核 CPU,也只有一个核心参与收集。使用并行收集器的话,垃圾收集的工做将分配给多个线程在不一样的 CPU 上同时进行。并行可让收集工做更快,缺点是带来的复杂性和内存碎片问题。
当 stop-the-world 垃圾收集器工做的时候,应用将彻底被挂起。与之相对的,并发收集器在大部分工做中都是并发进行的,也许会有少许的 stop-the-world。
stop-the-world 垃圾收集器比并发收集器简单不少,由于应用挂起后堆空间再也不发生变化,它的缺点是在某些场景下挂起的时间咱们是不能接受的(如 web 应用)。
相应的,并发收集器可以下降挂起时间,可是也更加复杂,由于在收集的过程当中,也会有新的垃圾产生,同时,须要有额外的空间用于在垃圾收集过程当中应用程序的继续使用。
垃圾回收器肯定了内存中哪些对象是活的,哪些是垃圾,它能够压缩内存,将全部的活动对象一块儿移动,并彻底回收剩余的内存。在压缩以后,在第一个空闲位置分配一个新对象是很容易和快速的。可使用一个简单的指针来跟踪对象分配的下一个位置。
与压缩收集器相反,不压缩的收集器只会就地释放空间,不会移动存活对象。优势就是快速完成垃圾收集,缺点就是潜在的碎片问题。通常来讲,从堆中进行分配比从压缩堆中分配更昂贵。可能须要在堆中搜索足够大的连续内存区域以容纳新对象。
第三种选择是复制收集器,它将活动对象复制到另外一个内存区域。这样作的好处是,原有区域的空间被清空了,这样后续分配对象空间很是迅速,缺点就是须要进行复制操做和占用额外的空间。
如下几个是评估垃圾收集器性能的一些指标:
在交互式程序中,一般但愿是低延时的,而对于非交互式程序,总运行时间比较重要。实时应用程序既要求每次停顿时间足够短,也要求总的花费在收集的时间足够短。在小型我的计算机和嵌入式系统中,则但愿占用更小的空间。
概念
最基础的垃圾收集算法就是“标记-清除算法”,如同它的名字,该算法分为“标记”和“清除”两个阶段。之因此是最基础算法是由于后续的几种收集算法都是基于这种思路并对其不足进行改进而获得的。
不足
第一,效率问题,标记和清除两个阶段的效率都不高
第二,空间问题,标记清除以后会产生大量不连续的内存碎片,空间碎片太多会致使须要分配较大对象时,没法找到足够的连续内存而不得不提早出发另外一次垃圾收集动做。
标记-清除算法示意图
概念
为了解决效率问题,复制算法便出现了,它将可用的内存按照容量等分红两块,每次只使用其中的一块,当这一块的内存用完了,就将存活的对象复制到另一块内存,而后将原有内存块的空间清理掉。这样每次只对整个半区内存进行垃圾回收,内存分配时就无需考虑内存碎片等复杂的状况了,只须要移动堆顶的指针,按顺序分配内存便可,实现简单,运行效率高。
不足
将内存大小一分为二,只使用其中一份,代价过高
复制算法示意图
使用场景
新生代正是采用复制算法进行垃圾收集,将新生代内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一个Survivor。当回收时,将存活的对象复制到另一块Survivor空间上,最后清理掉刚才使用过的Eden和Survivor空间。
HotSpot虚拟机默认Eden和Survivor的大小比例是,8:1:1,也就是新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。可是当Survivor空间不够用时,须要依赖其它内存(老年代)进行分配担保(Handle Promotion)。
若是另一块Survivor空间没有足够空间存放新生代收集下来的存活对象时,这些对象将直接经过分配担保机制进入老年代。
概念
复制收集算法在对象存活率较高的状况下就要进行较多的复制操做,效率将会变低。更关键的一点,若是不想浪费50%的空间,就须要额外的空间进行分配担保,以应对被使用的内存中的对象都100%存活的极端状况,因此老年代通常不能直接采用这种算法。
根据老年代的特色,“标记-整理算法(Mark-Compact)”就应运而生了,标记过程与“标记-清除算法”同样,但后续不是直接对可回收对象进行清理,而是让全部存活的对象都像一端移动,而后清理掉边界之外的内存。
标记-整理算法示意图
使用场景:老年代
当使用分代收集算法时,内存将被分为不一样的代(generation),最多见的就是分为年轻代和老年代。
在不一样的分代中,能够根据不一样的特色使用不一样的算法:
- 在新生代,每次垃圾收集时会发现有大批对象死去,只有少许存活,那就选择“复制算法”
- 在老年代,由于对象存活率高、没有额外空间为它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收
年轻代中的收集是很是频繁的、高效的、快速的,由于年轻代空间中,一般都是小对象,同时有很是多的再也不被引用的对象。
那些经历过屡次年轻代垃圾收集还存活的对象会晋升到老年代中,老年代的空间更大,并且占用空间增加比较慢。这样,老年代的垃圾收集是不频繁的,可是进行一次垃圾收集须要的时间更长。
对于新生代,须要选择速度比较快的垃圾回收算法,由于新生代的垃圾回收是频繁的。
对于老年代,须要考虑的是空间,由于老年代占用了大部分堆内存,并且针对该部分的垃圾回收算法,须要考虑到这个区域的垃圾密度比较低。
参考资料:
《深刻理解Java虚拟机:Java高级特性与最佳实践》
《Java内存管理白皮书》:http://www.oracle.com/technetwork/java/javase/memorymanagement-whitepaper-150215.pdf