内存空间是有限的,那么在程序运行时如何及时的把不再使用的对象清除将内存释放出来,这就是 GC 要做的事。
GC 的区域在哪里?
JVM 中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭, 栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理。因此, 我们的内存垃圾回收主要集中于 Java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的。
GC 的操作对象是什么?
需要进行回收的对象就是已经没有存活的对象,判断一个对象是否存活常用的有两种办法:引用计数和可达分析。
引用计数:每个对象有一个引用计数属性,新增一个引用时计数加 1, 引用释放时计数减 1,计数为 0 时可以回收。此方法简单,无法解决对象相互循环引用的问题。
可达性分析(Reachability Analysis):从 GC Roots 开始向下搜索, 搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。不可达对象。
在 Java 语言中,GC Roots 包括:
虚拟机栈中引用的对象。
方法区中类静态属性实体引用的对象。
方法区中常量引用的对象。
本地方法栈中 JNI 引用的对象。
GC 的时机是什么?
(1)程序调用 System.gc 时可以触发。
(2)系统自身来决定 GC 触发的时机(根据 Eden 区和 From Space 区的内存大小来决定。当内存大小不足时,则会启动 GC 线程并停止应用线程)。
GC 做了哪些事?
主要做了清理对象,整理内存的工作。
GC 常用算法
GC 常用算法有:
标记-清除算法,
标记-压缩算法,
复制算法,
分代收集算法。
目前主流的 JVM(HotSpot)采用的是分代收集算法。
标记-清除算法
为每个对象存储一个标记位,记录对象的状态(活着或是死亡)。分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行 GC 操作。
标记-压缩算法
标记-压缩法是标记-清除法的一个改进版。同样,在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间, 然后把剩下的所有对象全部清除。这样就达到了标记-整理的目的。
复制算法
该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。
分代收集算法
现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代(Young)和老年代(Tenure)。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高, 没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。