JVM 垃圾回收机制
本文参考自:
判断一个对象是否可以被回收
- 引用计数算法
- 可达性分析算法
- 方法区的回收
finalize
方法
四种引用类型
- 强引用:被强引用关联的对象不会被回收。
- 软引用:被软引用关联的对象只会在内存不足的情况下被回收。
- 弱引用:被弱引用关联的对象一定会被回收,只能活到下次垃圾回收之前。
- 虚引用:无法通过虚引用创建一个对象。为对象创建虚引用的唯一目的是在这个对象在被回收时受到一个系统消息。
垃圾收集算法
- 标记-清除算法
- 标记-复制算法
- 复制算法
- 分代收集算法
垃圾收集器
基本概念
- 并行与串行
- 单线程与多线程
主要收集器
CMS
CMS(Concurrent Mark Sweep,即多线程的标记-清除算法)
分为四个流程:
- 初始标记
仅仅是标记一下 GC Roots 能直接关联到的对象。 - 并发标记
进行 GC Roots Trancing 的过程。 - 重新标记
为了修正并发标记期间因用户线程继续运作而产生变动的那一部分的对象的标记记录。 - 并发清除
对标记出来的垃圾进行清除操作。
具有以下的缺点:
- 吞吐量低:低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率不高。
- 标记-清除算法导致的空间碎片,导致往往出现老年代空间剩余,但是无法找到一段连续的空间用于存储对象,而不得不进行一次 Full GC。
- 无法处理浮动垃圾,而导致 Concurrent Mode Failure。其中,浮动垃圾是指在并发清除阶段由于用户线程的继续运行而产生的垃圾。
G1(Garbage First)
G1 垃圾收集器是面向客户端的垃圾收集器,在多 CPU 和大内存场景下有很好的性能。
堆被分为新生代和老年代,G1 收集器可以直接对新生代和老年代一起回收。
G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。
通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。
每个 Region 都有一个 Remembered Set,用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set,在做可达性分析的时候就可以避免全堆扫描。
G1 收集器的运作大致分为以下的几个部分:
- 初始标记
仅仅是标记 GC Roots 能够直接标记到的对象。 - 并发标记
进行 GC Roots Tracing 的过程。 - 最终标记
为了修正在并发标记期间因用户线程继续运行而导致标记产生变动的那一部分标记记录。 - 筛选回收
首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。
具备如下的特点:
- 空间整合:从整体上看是基于标记-整理算法实现的收集器,从局部上看是居于复制算法实现的,这就意味着运行期间不会产生内存空闲碎片。
- 可预测的停顿:能够有用户自定义 GC收集器 上所消耗的时间。
其他收集器
graph TB
subgraph 新生代
Serial
ParNew
Parallel_Scaevnge[Parallel Scavenge]
end
subgraph 老年代
Serial_Old[Serial Old]
Parallel_Old[Parallel Old]
end
内存分配和回收策略
回收策略
- Minor GC
- Full GC
内存分配策略
一个人(对象)出来(new 出来)后会在Eden Space(伊甸园)无忧无虑的生活,直到GC到来打破了他们平静的生活。GC会逐一问清楚每个对象的情况,有没有钱(此对象的引用)啊,因为GC想赚钱呀,有钱的才可以敲诈嘛。然后富人就会进入Survivor Space(幸存者区),穷人的就直接kill掉。
并不是进入Survivor Space(幸存者区)后就保证人身是安全的,但至少可以活段时间。GC会定期(可以自定义)会对这些人进行敲诈,亿万富翁每次都给钱,GC很满意,就让其进入了Genured Gen(养老区)。万元户经不住几次敲诈就没钱了,GC看没有啥价值啦,就直接kill掉了。
进入到养老区的人基本就可以保证人身安全啦,但是亿万富豪有的也会挥霍成穷光蛋,只要钱没了,GC还是kill掉。
总结
- 对象优先在 Eden 分配
- 大对象直接进入老年代
- 长期存活的对象直接进入老年代
- 动态对象年龄判定
- 空间分配担保
Full GC 出触发条件
- 调用
System.gc()
- 老年代空间不足
- 空间分配担保失败
- JDK 1.8 之前的永久代空间不足
- Concurrent Mode Failure