垃圾回收算法
哪些内存需要回收
jvm的内存模型中将内存划分为程序计数器、虚拟机栈、本地方法栈、堆、方法区。
其中程序计数器、虚拟机栈、本地方法栈属于线程私有的内存空间,与线程的生命周期保持一致,不需要手动回收内存。
方法区中存放的是类的结构信息,对方法区的回收其实就是对类进行卸载,类的卸载必须满足以下三个条件:
- 类的实例及其子类实例已经全部被回收
- 加载类的类加载器已经被回收,通常加载类的加载器是用来加载应用中所有的类的,所以一般不会被回收。
- 类的Class对象没有被其他对象所引用
基于以上三点,方法区能回收的内存空间是比较小的,可以通过虚拟机参数-Xnoclassgc来控制。
综上,垃圾回收的重点区域为堆。
怎么判断对象已死
堆上面存储的是对象,那么这些对象什么时候应该被收回呢?当然是没有其他对象引用它时,也就是对象已死。
通常有下面两种方法来判断哪些是活着,哪些对象已死:
- 引用计数法
- 可达性分析
引用计数法
引用计数法是通过为每个对象创建一个引用计数器,当这个对象每被其他对象引用一次时,引用计数器就会加1,当引用失效时,引用计数器就会减1,当引用计数器为0时,代表这个对象可以被回收了。
缺点:无法解决循环引用问题,如果A引用B,B引用A,那么这两个对象的引用计数器将会一直为1,永远无法被回收。
可达性分析
可达性分析就是通过一系列的GC Roots对象作为根节点,从这些根节点开始,根据引用关系向下搜索,搜索过程中所走过的路径称为引用链,如果某个对象与GC Roots对象之间没有任务引用链相连,那么就可以判断这个对象可以被回收。
可以作为GC Roots的对象包括以下几种:
- 虚拟机栈中的局部变量所引用的对象
- 方法区中的静态变量
- 方法区中的常量
- 本地方法栈中所引用的对象
- jvm内部引用的对象,如基本数据类型对应的包装类,系统类加载器
- 被synchronized持有的对象
注意:
- 我们这里说的是活跃的引用,而不是对象,对象是不能作为GC Roots的。
- GC过程是找出所有活对象,并把其余空间认定为“无用”;而不是找出所有死掉的对象,并回收它们占用的空间。
怎么回收
Minor GC/Young GC:指发生在新生代的垃圾收集动作,非常频繁,速度较快。
Major GC/Old GC:指发生在老年代的GC,出现Major GC,经常会伴随一次Minor GC,同时Minor GC也会引起Major GC,一般在GC日志中统称为GC,不频繁。
Full GC:指发生在堆和方法区的GC。
标记-清除算法
标记清除法将垃圾回收分为两个阶段:标记阶段和清除阶段。在标记阶段,首先通过根节(GC Roots),标记所有从根节点开始的可达对象,因此未被标记的对象就是未被引用的垃圾对象。然后在清除阶段,清除所有未被标记的对象。
标记清除算法的缺点:产生大量的空间碎片。
标记-复制算法
标记-复制算法一般简称为复制算法。
复制算法将原有的内存空间分为两块相同的存储空间,每次只使用一块,在垃圾回收时,将正在使用的内存块中存活对象复制到未使用的那一块内存空间中,之后清除正在使用的内存块中的所有对象,完成垃圾回收。
复制算法适用于垃圾对象较多的场景,没有碎片空间。但是复制算法的代价是将系统内存空间折半,只使用一半空间,而且如果内存空间中垃圾对象少的话,复制对象也是很耗时的,
标记-整理算法
标记-整理算法在标记-清除算法的基础上做了优化。首先从根节点开始,对所有可达的对象做一次标记,然后将所有的存活对象压缩到内存空间的一端,最后清理边界外所有的空间。这样做避免的碎片的产生,又不需要两块相同的内存空间,因此性价比高。
分代算法
分代算法将内存空间根据对象的特点不同进行划分,选择合适的垃圾回收算法,以提高垃圾回收的效率。
新生代的特点是:对象朝生夕灭,大约90%的对象会很快回收,因此,新生代比较适合使用复制算法。
新生代分为一块较大的Eden空间和两块较小的survivor空间,当回收时,将Eden空间和其中一块Survivor空间中存活的对象复制到另一块Survivor,最后清理掉Eden空间和刚刚使用过的Survivor空间。
老年代的存活率是很高的,如果依然使用复制算法回收老年代,将需要复制大量的对象。这种做法是不可取的,根据分代的思想,对老年代的回收使用标记清除或者标记压缩算法可以提高垃圾回收效率。
更多精彩内容关注本人公众号:架构师升级之路