在 Java 对对象进行回收之前,需要判断哪些对象已死,哪些对象存活,常用的判断方法有两种: 引用计数法
和可达性分析法
引用计数法就是在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一,当引用失效时就减一。当计数器为 0 时,对象便不再被引用。
引用计数法虽然消耗了一些空间,但是原理简单,判定高效,在绝大多数情况下是一个不错的算法。但是引用计数法需要考虑到很多例外的情况,必须要配合大量的额外工作才能正确的工作,比如单纯引用计数法就很难解决对象之间相互循环引用的问题。
通过一系列 “GC Roots” 的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,向下搜索的过程中所走过的路径称为 引用链(Reference Chain)
。如果对象没有任何一条引用链路劲连接,即从任何一个 GC Roots 出发都不能到达该对象。
在 Java 中,GC Roots 包含:
在 JDK 1.2 后,扩充了引用的概念,将引用依强度依次分为 强引用(Strongly Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)
强引用 强引用是指在程序代码中普遍存在的引用类型的赋值。一般只要强引用关系还在,垃圾收集器就永远不会被回收掉。
软引用 被软引用关联的对象,会在系统即将发生内存溢出异常前进行回收。
弱引用 被弱引用关联的对象只能生存到下一次垃圾收集为止。
虚引用 被虚引用关联的对象,既不会影响对象的生存时间,也无法通过虚引用获取对象实例,只是在该对象被回收时收到一个系统的通知。
在进行垃圾回收是,首先会对对象进行可达性分析,若果发现对象没有与 GC Roots 相连接的链路,将会对对象进行第一次标记。然后看标记的对象是否覆盖了 finalize 方法或者 finalize 方法已经被虚拟机调用过了,如果没有覆盖或者已经被调用过了,则直接回收,如果没有则将对象放入到 F-Queue 中主备通过 Finalizer 线程去执行这些方法,在执行完之后,垃圾收集器会对对象进行二次标记,如果对象依旧北邮被 GC Roots 引用,则对象会被回收
finalize 方法只能被 jvm 调用一次,且对象被标记后只有通过 finalize 方法来逃脱,且只有一次逃离机会。
在方法区 (又称元数据或永久代) 中,垃圾的回收对象为两种,废弃的常量
和不再使用的类型
。
废弃常量的判定: 判定一个常量是否是废弃常量只需要判断虚拟机中有没有其他地方引用这个常量。
不使用类型的判定:
当前虚拟机的垃圾收集器,大多都遵循了“分代收集理论”,该理论依据实际情况和法则建立,也是建立在两个分代假说之上
分代假说
Java 堆中垃圾回收分区
在 Java 堆中,会将新生代分成一块较大的 Eden 区,和两块较小的 Survivor 区。在 HotPots 虚拟机中,这一比例默认为为 8:1:1.
垃圾回收的方式
标记清除算法会首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象
缺点:
标记复制算法会将内存按照容量大小划分为相等的两块,每次只使用其中的一块,当这一块用完了,就将还活着的对象复制到另一块上,然后再把已使用过的空间清理掉。
缺点:
该算法常用于 Java 堆中新生代的垃圾回收方法。每次分配内存只使用 Eden 和 一块 Survivor 区,当进行垃圾收集时,会将 Eden 和 Survivor 区上存活的对象复制到另一块 Survivor 区上。
在对象存活较多时,就需要进行较多的复制操作,会降低回收的效率。标记整理算法是对存活的对象进行标记,然后对对象进行整理,这样既减少了复制的次数,也避免了空间的浪费。