所谓GC
Java的垃圾回收机制之前一直觉得太接近底层复杂不好理解,最近看过好多别人的博客和文章,自己有了一个系统的理解。
垃圾回收机制是Java很重要的一部分,JVM会帮我们做好垃圾回收,所以我们日常开发中不太常用垃圾回收。只有在某些特定的场合,常见数据库连接完成需求后对资源进行回收。
什么是垃圾?
Java中会通过两种方式判断一个资源是否已经需要回收了。就是计数法和可达性分析算法这两种。
计数法
给对象添加一个计数器,如果引用一次计数增加1,如果引用失效一次,计数就减少1,直到这个对象的计数器中的数值为0的时候,这个对象就会被判定为垃圾。
这种方式虽然执行效率高,对程序的影响比较小,但是如果有循环调用就会导致内存泄漏。
可达性算法分析
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
在可达性分析法中不可达的对象,它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
怎样回收?
既然已经可以判定资源是否属于垃圾需要回收,那么回收是怎样实现的?
其实在Java中存在着四种垃圾回收算法,标记清除算法、复制算法、标记整理算法以及分代回收算法。
标记清除法
该算法分为“标记”和“清除”两个阶段:标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。
虽然这种方法效率比较高,但是清除过后内存中会有很多不连续的碎片。
复制算法
复制算法就是每次在内存中分出两小块,其中一块作为主存储资源,当第一块内存使用完后会将这块内存的可用资源复制到另一块内存中去,然后清除掉第一块内存。
这种方式解决了内存碎片化问题,提高了顺序分配效率。但是如果有时候内存中大多数或者全部资源都存活时,就要浪费一半的时间。
标记整理算法
为了解决复制算法的缺陷,充分利用内存空间,提出了标记整理算法。该算法标记阶段和标记清除一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
分代回收算法
当前虚拟机的垃圾收集都采用分代收集算法,这种算法就是根据具体的情况选择具体的垃圾回收算法。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。