目录
标记阶段:
·引用计数算法
原理:对于每一个对象,保存一个整型的引用计数器属性,有任何对象引用了该对象,引用计数器值加1,当引用失效时,引用计数器值减1。当引用计数器值为0时,表示对象不在被使用,可以进行回收。
有致命的缺点:无法解决循环引用的问题。如图1:当外部指针p断开时,发现next对象的引用计数器值永远不会为0,对象无法被回收,造成内存泄漏。导致Java虚拟机中没有使用该算法。
![](https://img-blog.csdnimg.cn/20210624111955700.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Mzg0MzA0,size_16,color_FFFFFF,t_70)
·可达性分析算法(根搜索算法)
原理:以根对象集合(GC Roots)为起始点,从上至下搜索对象,判断对象是否被根对象直接或间接的引用着,如果对象没有被根对象集合直接或间接的引用着,则是不可达的。
使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的环境中进行。这也是导致垃圾回收时,导致stop the world的原因。
在可达性分析算法中,只有直接或间接被根对象连接着的对象,才是存活对象。如图2:
![](https://img-blog.csdnimg.cn/20210624141827913.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Mzg0MzA0,size_16,color_FFFFFF,t_70)
哪些结构可以作为根对象(GC Roots):
- 虚拟机栈中引用的对象。比如:各个线程被调用方法中使用到的参数、局部变量等。
- 本地方法栈内JNI(通常说的本地方法)引用的对象。
- 方法区中类静态属性引用的对象。比如:Java类的引用类型静态变量。
- 方法区中常量引用的对象。比如:字符串常量池(String Table)里的引用。
- 所有被同步锁synchronized持有的对象
- Java虚拟机内部的引用。基本数据类型对应的Class对象,一些常驻的异常对象(如:NullPointerException、OutOfMemoryError),系统类加载器。
清除阶段:
·标记-清除(Mark-Sweep)算法
当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被称为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。
标记:Collector从引用根节点开始遍历,标记不被回收的对象,一般是在对象的Header中记录为可达对象。
清除:Collector对堆内存从头到尾进行线性遍历,将对象Header中没有标记为可达的对象,将其回收。
![](https://img-blog.csdnimg.cn/20210705133041932.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Mzg0MzA0,size_16,color_FFFFFF,t_70)
缺点:回收后的内存是分散的,碎片状的,需要维护一个空闲列表(空闲列表:把一个地址告诉给空闲列表,空闲列表来记录这些地址是空闲的,之后数据就直接覆盖在该地址的数据)。
·复制算法
原理:将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。
![](https://img-blog.csdnimg.cn/20210705135828223.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Mzg0MzA0,size_16,color_FFFFFF,t_70)
优点:
- 保证空间连续性。
- 不需要标记过程。
- 适用于可达对象少,垃圾对象多的场景,比如surviver区(from区和to区)。
缺点:
- 需要两倍的内存。
- 从栈中的引用地址需要改变(因为对象的地址改变了)。
- 当可达的对象很多时,复制算法就不适用了。
·标记-压缩/整理 算法
标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark-Sweep-Compact)算法。
![](https://img-blog.csdnimg.cn/20210705151808481.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Mzg0MzA0,size_16,color_FFFFFF,t_70)
与标记-清除算法的区别
二者的本质差异在于标记-清除算法是一种非移动式的回收算法,标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策。
可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。
优点:
- 解决了标记-清除算法的 “碎片” 问题。
- 解决了复制算法两倍内存的问题。
缺点:
- 效率没有复制算法高。
- 移动了对象,需要改变引用地址。
分代回收思想
新生代垃圾回收频率高,存活对象少,采用复制算法。
老年代垃圾回收频率低,存活对象多,采用标记-清除算法或与标记-整理算法混合使用。
增量收集算法
如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。
总的来说,增量收集算法的基础仍是传统的标记-清除和复制算法。增量收集算法通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作。