垃圾回收机制(Garbage Collection,GC)是 java语言的特性,有了 GC,不用再像 c/c++ 那样麻烦地考虑内存的分配和释放。其实,垃圾回收机制的历史远远要比java的历史久远,因为在1960年诞生的Lisp是第一门应用垃圾回收机制的语言。虽然有了垃圾回收机制帮我们完成垃圾回收,但是我们还是得了解 GC 的运行机制,以便于在开发中排查内存泄露和内存溢出等问题,通过优化 GC 来提供应用的性能。
即是对过内存了解的不多,您可能也听过内存通常分为栈内存和堆内存。栈和堆是两种不同的数据结构,我们将物理的内存抽象为这两种数据结构,便于管理内存(如何访问内存以及内存地址间的关系)。垃圾回收机制主要作用于 java 的堆(Heap)内存,堆内存也用于存放 java 对象实例,所以很多时候我们也把 java堆成为GC堆。
在程序的运行时,需要在堆内存中的对象越来越多,堆内存中没有足够的空间存放新对象。就得想办法来回收已不再用的对象,来释放堆内存的空间,那么垃圾回收机制在进行垃圾回收前,就得判断哪些实例已不再使用,哪些实例还要继续使用,我们用可达算法来判断对象是否存活。
在主流商用语言(如Java、C#)的主流实现中, 都是通过可达性分析算法来判定对象是否存活的: 通过一系列的称为 GC Roots 的对象作为起点, 静态属性和在栈内存中方法中的本地变量所引用的对象。然后向下搜索,搜索所走过的路径称为引用链/Reference Chain, 当一个对象到 GC Roots 没有任何引用链相连时, 即该对象不可达, 也就说明此对象是不可用的, 如下图: Object5、6、7 虽然互有关联, 但它们到GC Roots是不可达的, 因此也会被判定为可回收的对象。
那么我们具体如何划分堆内存的呢?现在通常做法,将内存划分一定区域,我们通常按对象存活的时间对堆内存中的对象进行划分,这就是分代算法。也是流行的 GC 算法。我们这里需要了解一个规律,就是大多数的对象都是来也匆匆去也匆匆,他们的存活是短暂的。所以我们新看 GC 是如何处理年轻代的内存的。我们将年轻代的内存再次细分为三部分 Eden 、 survivor to 和 survivor from 三个部分,首先我们还需了解我 GC 时通常是先对内存进行标记,标记出哪些内存是不可达的,也就是他们将被 GC 掉,GC 掉那些不可达(没有被引用的)对象后,在堆内存中会留下许多空位,我们还需要处理这些空位,类似 windows 的清理磁盘碎片。我们也需要清理掉这些空位,让内存连续,这个过程大家可以理解为压缩内存。
当 Eden 区域新生的对象过多是,就会触发 Minor GC 来清理 Eden 区的对象
我们将标记为可达对象移动到 survivor 区域,绿色的表示存活的对象,然后灰色对象表示不可达的对象。
我们清除掉那些不再被引用的对象(灰色表示),这些绿色对象经历一次 minor GC 的浩劫存活下来了。
随着 Eden 的新的对象有不断涌现,我们又需要进行 minor GC 这一次我们依旧以同样的方法来处理 Eden 中对象。
1. 将 survivor 1 中的可达对象移动到 survivor 2 中,剩下的都是不可达的对象
将 survivor 2 中的的内存标记为 2,然后在下一次 minorGC 将为 1 的存活的对象移动到 survior 2 中,这样好处就是我们通过来回移动对象,可以无需考虑连续性,这样我们通过牺牲空间来换取效率,无需考虑如何压缩内存。
这样反复几次,如果还存活的对象反复次数超过阀值就会晋升到老年代
网友评论