一、判断对象是否已死
判断对象是否还存活着有如下两种方式
1、引用计数法
栈中的变量或者方法区中的类变量都会指向堆中的对象,我们会给堆中的对象添加一个引用计数器,每当此对象被某个地方引用的时候,就会+1,引用失效-1,当计数器为0,失效。
缺点是无法检测出循环引用。譬如有A和B两个对象,他们都互相引用,除此之外都没有任何对外的引用,那么理论上A和B都可以被作为垃圾回收掉,但实际如果采用引用计数算法,则A、B的引用计数都是1,并不满足被回收的条件,如果A和B之间的引用一直存在,那么就永远无法被回收了
2、可达性分析
hotspot虚拟机默认的判断方式。是通过一系列的“GC Roots”的对象作为起始点,从起始点开始向下搜索到对象的路径。搜索所经过的路径称为引用链(Reference Chain),当一个对象到任何GC Roots都没有引用链时,则表明对象“不可达”,即该对象是不可用的。
- 在Java语言中,可作为GC Roots的对象包括下面几种:
- 栈帧中的局部变量表中的reference引用所引用的对象
- 方法区中static静态引用的对象
- 方法区中final常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
- Java虚拟机内部的引用, 如基本数据类型对应的Class对象, 一些常驻的异常对象(比如 NullPointExcepiton、 OutOfMemoryError) 等, 还有系统类加载器。
-
所有被同步锁(synchronized关键字) 持有的对象。
如下图:
image.png
- 可达性分析算法
- 第一步:可达性分析后发现没有与GC Roots相连接的引用链, 那它将会被第一次标记
- 第二步:判断是否重写finalize()方法并且是否被jvm调用过,如果未重写或重写但被调用过就第二次标记,如果重写了但还未调用过,就执行finalize()方法,该对象将会被放置在一个名为F-Queue的 队列之中。
二、垃圾收集算法
1.分代收集理论
分代收集名为理论, 实质是一套符合大多数程序运行实际情况的经验法则, 它建立在两个分代假说之上:
1) 弱分代假说(Weak Generational Hypothesis) : 绝大多数对象都是朝生夕灭的。
2) 强分代假说(Strong Generational Hypothesis) : 熬过越多次垃圾收集过程的对象就越难以消亡。
- 部分收集(Partial GC) : 指目标不是完整收集整个Java堆的垃圾收集, 其中又分为:
- 新生代收集(Minor GC/Young GC): 指目标只是新生代的垃圾收集。
- 老年代收集(Major GC/Old GC): 指目标只是老年代的垃圾收集,目前只有CMS收集器会有单 独收集老年代的行为。
- 混合收集(Mixed GC): 指目标是收集整个新生代以及部分老年代的垃圾收集。 目前只有G1收集器会有这种行为。
- 整堆收集(Full GC) : 收集整个Java堆和方法区的垃圾收集
2.标记-清除
标记-清除算法有两个不足之处:
第一个是执行效率不稳定, 如果Java堆中包含大量对 象, 而且其中大部分是需要被回收的, 这时必须进行大量标记和清除的动作, 导致标记和清除两个过 程的执行效率都随对象数量增长而降低;
第二个是内存空间的碎片化问题, 标记、 清除之后会产生大 量不连续的内存碎片, 空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找 到足够的连续内存而不得不提前触发另一次垃圾收集动作。
3.标记-复制
将可用内存按容量划分为大小相等的两块, 每次只使用其中的一块。 当这一块的内存用完了, 就将还存活着 的对象复制到另外一块上面, 然后再把已使用过的内存空间一次清理掉。
- 不足
1)需要提前预留一半的内存区域用来存放存活的对象(经过垃圾收集后还存活的对象),这样导致可用的对象区域减小一半,总体的GC更加频繁了
2)如果出现存活对象数量比较多的时候,需要复制较多的对象,成本上升,效率降低
3)如果99%的对象都是存活的(老年代),那么老年代是无法使用这种算法的。
4.标记-整理
针对老年代对象的存亡特征提出了另外一种有针对性的“标记-整理”算法, 其中的标记过程仍然与“标记-清除”算法一样, 但后续步骤不是直接对可回收对象进行清理, 而是让所有存活的对象都向内存空间一端移动, 然后直接清理掉边界以外的内存 。
网友评论