原文链接
回收的是什么?
答:运行程序中没有任何指针援用的对象,这个对象就是须要被回收的垃圾
垃圾回收算法
标记阶段
- 在 GC 执行垃圾回收之前,首先须要辨别出内存中那些是存活的对象,那些是曾经死亡的对象。只有被标记为已死亡的对象,GC 才会在执行垃圾回收时,开释掉其存活所占用的内存空间。此过程被称为 垃圾标记阶段
- 当一个对象曾经不再被任何的存活对象持续援用时,就能够宣判为曾经死亡
- 判断对象存活的有两种形式:援用计数算法 和可达性剖析算法
援用计数算法(Reference Counting)
- 形容:对每一个对象保留一个整型的援用计数器属性,用于记录对象被援用的的状况
-
长处:
- 实现简略,垃圾对象便于辨识
- 断定效率高,回收没有提早性
-
毛病:
- 须要独自的字段存储计数器,减少了存储空间
- 每次赋值都须要从新更新计数器,减少了工夫开销
- 无奈解决 循环援用
可达性剖析算法
- 解决了 援用计数算法 中循环援用的问题,避免内存泄露
- 可达性剖析算法是以根对象汇合(GC Roots)为起始点,依照从上至下的形式搜寻被根对象汇合所连贯的指标对象是否可达
- 应用该算法后,内存中的存活对象都会被根汇合间接或间接的连贯着,搜寻走过的门路称为 援用链
- 如果没有任何援用链,则不可达,阐明对象已死亡,能够标记为垃圾对象
GC Roots 蕴含以下几类元素:
- 虚拟机栈中援用的对象
- 本地办法(native 办法)中援用的对象
- 办法区中常量援用的对象
- 被同步锁
synchronized
持有的对象 -
Java 虚拟机外部的援用
- 根本数据类型对应的 Class 对象常驻异样对象和零碎类加载器
- 反映 Java 虚拟机外部状况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等
finalization 机制
- 提供对象被销毁之前的自定义解决逻辑
- 当垃圾回收器发现没有援用指向一个对象,总会先调用这个对象的 finalize()办法
- finalize()办法容许在子类中被重写,用于在对象被回收时进行资源开释。例如:敞开文件,断开数据库连贯
革除阶段
当胜利辨别出内存中存活对象的和死亡对象后,接下来就会执行垃圾回收,开释掉无用对象所占用的内存空间,以便有足够的空间为新对象所调配
标记 - 革除算法(Mark-Sweep)
执行过程:当堆中的内存空间被耗尽的时候,就会进行整个程序(stop the world),而后进行标记和革除工作。
标记:Collector 从援用根节点开始遍历,标记所有被援用的对象,并将这些对象记录在 Header 中。(找出在程序中被援用的对象,并革除其余的)
革除:Collector 从堆内存中从头到尾的进行线性遍历,如果发现某个对象在 Header 中没有标记为可达对象,则将其回收。(将不可达对象寄存在闲暇列表中,当有新对象进入时,将其地位笼罩)
毛病:
- 效率不高
- 在进行 GC 时,须要进行整个应用程序,导致用户体验差
- 清理进去的闲暇内存是不间断的,产生内存碎片且须要保护一个闲暇列表
复制算法(Copying)
执行过程:当 A 空间同时存在可达和不可达对象时,将可达对象复制到 B 空间并保障空间的间断,后革除 A 空间的所以对象
长处:
- 没有标记革除过程,实现简略
- 将存在援用关系的对象复制过来后,保障空间的连续性,不会存在碎片
毛病:
- 须要两倍的内存空间
- 对于存活对象的数量远大于垃圾对象数量时,此革除算法效率差。
标记 - 压缩算法(Mark-Compact)
执行过程:
- 从根节点开始标记存在援用关系的对象
- 将这些对象压缩到内存的一端并按程序排放
- 革除边界外的所以对象
长处:
- 对于“标记 - 革除算法”,因为应用了压缩性能,当有新对象分配内存时,JVM 只须要持有一个内存的起始地址即可
- 对于“复制算法”,缩小了内存空间的占用
毛病:
- 比照“复制算法”,效率低
- 在挪动存在援用关系的对象时,须要调整援用的地址
- 挪动过程中,须要全程暂停用户程序(stop the world)
分代收集算法
依据年老代和老年代的特点应用不同的内存回收算法
年老代:
针对年老代空间小,对象生命周期短,收集频繁和存活率低的特点,应用复制算法能够高效实现内存回收,复制算法使用率不高的问题,通过两个 survivor 的设计能够无效的缓解。
老年代:
老年代是空间大,对象生命周期长,存活率高和回收不频繁,应用标记 - 革除算法或标记 - 革除算法和标记 - 压缩算法联合实现内存空间的回收。