更多 Java 虚拟机方面的文章,请参见文集《Java 虚拟机》
对象的状态
- 可达状态,被引用变量引用
-
可恢复状态,没有被引用,系统调用
finalize()
后重新获得了引用 -
不可达状态,没有被引用,系统调用
finalize()
后没有获得引用,GC 回收
垃圾对象的判断
引用计数算法
给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加 1,当引用失效时,计数器值就减1,任何时刻计数器都为 0 的对象就是不可能再被使用的。
引用计数算法的实现简单,判定效率也很高,在大部分情况下它都是一个不错的选择。但 Java 语言并没有选择这种算法来进行垃圾回收,主要原因是它很难解决对象之间的相互循环引用问题。根搜索算法
Java 都是采用根搜索算法来判定对象是否存活的。这种算法的基本思路是通过一系列名为 GC Roots 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连时,就证明此对象是不可用的。
GC Root,它不是对象图里面的对象,他是一组特别管理的指针,是 GC tracing 的起点,假如两个对象(objectA
与objectB
)互相引用,然后其实后面又没有用到他们了比如objectA=null
,objectB=null
,此时他们到 GC Root 的路径是断的,只是他们互相之间是可以到达的,所以可以回收。
GC 的起点 GC Roots
- JVM 栈中 local variable 引用的对象
- 类静态属性 static 引用的对象
- 常量引用的对象
- 本地方法栈中 JNI 引用的对象
GC 的任务
- 分配内存
- 确保被引用的对象内存不被回收
- 确保不被引用的对象内存被回收
STW
GC 是复杂且耗时的操作,GC 时,整个应用要被暂时中止,即 STW(Stop the World),因为 GC 需要更新整个应用中所有对象的实际内存地址。
System.gc()
System.gc();
提示 JVM 进行 GC,但是不能确保 JVM 会这么做,JVM 可能会忽略,取决于不同的 JVM 实现。
obj.finalize()
obj.finalize()
类似于析构函数,在一个对象被真正回收之前,执行一些清理工作。
在三种情况下 obj.finalize()
会调用:
- 对象被 GC 时自动调用,比如运行
System.gc();
- 程序退出时为每个对象自动调用一次
obj.finalize()
- 显式的调用
obj.finalize()
方法
JVM 不保证 finalize() 一定被调用,也就是说 finalize() 的调用是不确定的
回收算法
分代回收算法
内存中的区域被划分成不同的世代,对象根据其存活的时间被保存在对应世代的区域中。
3个世代:新生代(Young Generation)、老年代(Old Generation)和永久代(Perm Generation),如下图所示:
对于不同的世代可以使用不同的垃圾回收算法。
新生代 Young Generation 进一步分为 Eden 区和 两个 Survivor 区:
- Eden 区:是一块连续的空闲内存区域,在这里进行对象内存的分配。在 Eden 区进行内存分配非常快,因为不需要进行可用内存块的查找。
-
两个 Survivor 区:一个叫做 From,另一个叫做 To。两个 Survivor 区中始终有一个是空闲的。
假设当前 To 区是空白的,GC 时,Eden 区和 From 区中存活的对象根据其存活的时间被复制到 To 区和 Old 区。随后一次性回收 Eden 区和 From 区,From 与 To 角色互换。
老年代(Old Generation):
任何从新生代中的 Survivor 区中幸存下来的对象会被送往老年代。老年代通常比新生代大很多。
永久代(Perm Generation):
永久代存储类的定义及常量池,是一块连续的堆空间,默认 64M。
永久代的 GC 与 老年代 的 GC 捆绑在一起,无论谁满了,都会触发永久代与老年代的 GC。
在 Java 8 中 永久代(Perm Generation) 被 Metaspace 所取代,默认128M。
对于年老代(Old Generation)和永久代(Perm Generation)区域,采用 标记-清除-压缩(Mark-Sweep-Compact)算法:
- 标记:遍历堆空间,找出当前还存活的对象,进行标记
- 清除:回收不可用的对象
- 压缩:把存活对象的内存移动到整个内存区域的一端,使得另一端是一块连续的空闲区域
Minor GC VS Major GC VS Full GC
- Minor GC 即新生代 GC:频繁,速度快。因为 Java 对象大多都生命周期很短。
- Major GC 即老年代 GC:不频繁,速度慢。因为老年代中的对象生命周期比较长。其速度一般会比 Minor GC 慢 10 倍以上。
- Full GC 即整个堆空间,包括新生代 GC 和老年代 GC
- 另外,如果分配了 Direct Memory,在进行 Full GC时,会顺便清理掉 Direct Memory 中的废弃对象
标记 - 清除算法 - 不支持压缩
最基础的收集算法,它分为“标记”和“清除”两个阶段:首先标记出所需回收的对象,在标记完成后统一回收掉所有被标记的对象,它的标记过程其实就是前面的根搜索算法中判定垃圾对象的标记过程。
标记 - 整理算法 - 支持压缩
该算法标记的过程与标记 - 清除算法中的标记过程一样,但对标记后出的垃圾对象的处理情况有所不同,它不是直接对可回收对象进行清理,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存。
复制式回收算法
将堆分为 A 和 B 两个部分。遍历 A,将 A 中的可达对象复制到 B,然后一次性回收 A。
引用:
Java 垃圾收集机制