文章目录
1. 引用计数法
- 引用和去引用伴随加法和减法,影响性能
- 很难处理循环引用
2. 标记清除
-
原理
标记-清除算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。 -
图例
3. 标记压缩
标记-压缩算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间
。
4. 复制算法
- 与标记-清除算法相比,复制算法是一种相对高效的回收方法
- 不适用于存活对象较多的场合 如老年代
- 将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收
- 空间浪费
- 整合标记清理思想
5. GC算法总结
- 效率问题
标记和清除过程的效率都不高
- 空间问题
标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作
- 优点:
堆的利用效率高
- 缺点:
需要多次搜索整个堆,所以,吞吐量较低(耗时较长)需要多次搜索整个堆,所以,吞吐量较低(耗时较长)
-
复制算法
实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半
-
标记-清除
和标记-压缩
对比标记–压缩算法的标记阶段和标记–清除算法的标记阶段是一致的,就不再重复。使用标记–压缩算法时,标记完可达对象之后,我们不再遍历所有对象清扫垃圾了,我们只需要将所有存活对象向“左”靠齐,让不连续的空间变成连续的,这样就没有内存碎片了。不仅如此,因为不再连续的空间变成连续的,内存分配也更快速了。
对于标记–清除算法来说,因为内存中有碎片,空闲内存不再连续,为了分配内存,系统内可能要维护着一个空闲内存空间的链表。当需要分配内存时,会遍历这个链表,找到一个够大的内存块,然后将其分成两份,一份用作当前的分配,另一份放回链表(这样有造成更多的内存碎片,也有一些策略并不是按顺序查找,找到够大的就好,有可能是找到一个更好的空闲内存块为止)。而对于标记–压缩算法,内存空间是连续的,我们只需要一个指针标记出下一次分配工作要从哪里开始就可以了,分配后将指针递增所分配对象的大小,这个工作是非常快速的,而且不用维护那个空间内存链表了。
这样一看好像标记–压缩算法绝对的优于标记–清除算法,那标记–清除还有啥存在的必要了呢?不过要记住的一点是标记–压缩算法为了达到压缩的目的,是需要移动对象的,这会有性能消耗的,这样所有对象的引用都必须更新。看来有利必有弊。
所有的算法,需要能够识别一个垃圾对象,因此需要给出一个可触及性的定义
6. 可触及性
- 从根节点可以触及到这个对象
- 一旦所有引用被释放,就是可复活状态
- 因为在finalize()中可能复活该对象
- 在
finalize()
后,可能会进入不可触及状态 - 不可触及的对象不可能复活
- 可以回收
- 避免使用finalize(),操作不慎可能导致错误
- 优先级低,何时被调用, 不确定,何时发生GC不确定,可以使用
try-catch-finally
来替代它
- 栈中引用的对象
- 方法区中静态成员或者常量引用的对象(全局对象)
- JNI方法栈中引用对象
7. Stop-The-World
- Java中一种全局暂停的现象
- 全局停顿,所有Java代码停止,native代码可以执行,但不能和JVM交互
- 多半由于GC引起
- Dump线程
- 死锁检查
- 堆Dump
- 类比在聚会时打扫房间,聚会时很乱,又有新的垃圾产生,房间永远打扫不干净,只有让大家停止活动了,才能将房间打扫干净。
- 长时间服务停止,没有响应
- 遇到HA系统,可能引起主备切换,严重危害生产环境