JVM 垃圾回收
JVM 的内存结构
JVM 的内存区域按功能分为多个部分,其中堆(Heap) 和方法区(Method Area,JDK8 后为元空间 Metaspace) 是垃圾回收的主要目标(因为这两个区域的内存分配和释放是动态的)。其他区域(如程序计数器、虚拟机栈、本地方法栈)是线程私有且生命周期与线程一致,无需 GC(GC 就是垃圾回收)。
- 堆:存储对象实例(几乎存储所有对象都在这里分配),是 GC 最核心的区域。堆按对象存活周期可分为:
- 年轻代:分为 Eden 区和两个 Survivoe 区,用于存储新创建的对象,对象存活率低,回收频繁
- 老年代:存储存活时间较长的对象(多次年轻代回收仍然存活的对象就会被存储到老年代),存活率高,回收频率低
- 方法区:存储类信息,常量、静态变量,也会发生垃圾回收
垃圾回收
垃圾回收的本质是自动释放不再被使用地内存,解决手动管理内存容易出现的内存泄漏、内存溢出问题。我们需要考虑两个问题:哪些对象是垃圾?如何高效地回收这些垃圾。
- STW:在垃圾回收的过程中,用户所有线程暂停执行
- 内存分配策略:对象优先在 Eden 区分配;Eden 区满时触发 Minor GC(年轻代回收);多次 Minor GC 后仍存活的对象进入老年代;大对象直接进入老年代(避免频繁复制)。
- 类卸载:方法区的垃圾回收主要是 “类卸载”,当该类的实例已经被回收,该类就会被回收
判断垃圾:JVM 需要先确定哪些对象已经死亡
- 引用计数法:给每个对象设置一个引用计数器,被引用时+1,引用失效-1;当计数器为 0 时,认为对象可以回收。但是当 A 引用 B,B 引用 A 时,两者都不再被其他对象引用,计数器永远不为 0,导致无法回收。
- 可达性分析算法:以 “GC Roots” 为起点,向下搜索引用链(对象之间的引用关系形成的链条);所有能被 GC Roots 直接或间接引用的对象都是 “存活的”,否则为 “垃圾”。GC Roots 必须时全局可访问且不会被回收的对象。
若对象 A 被 GC Roots 引用,A 引用 B,B 引用 C,则 A、B、C 均存活;若 A 不再被 GC Roots 引用,则 A、B、C 均为垃圾。
引用又分为 4 种强度: - 强引用:最普通的引用(如 Object obj = new Object()),只要强引用存在,对象永远不会被回收。
- 软引用:用 SoftReference 包装,内存不足时才会被回收(适合缓存场景)。
- 弱引用:用 SoftReference 包装,内存不足时才会被回收(适合缓存场景)。
- 虚引用:用 PhantomReference 包装,唯一作用是在对象被回收时收到通知
垃圾回收算法
清除算法
通过可达性分析标记所有存活对象;遍历内存,回收所有未被标记的对象
优点:简单直接,无需移动对象
缺点:回收后空间不连续,大对象可能无法分配;效率低,标记和清除都需要遍历所有对象
复制算法
将内存分为大小相等的两块,只使用其中的一块;标记存活对象后,将所有存活对象复制到另一块,然后清空 A 块;
优点:复制后对象连续存储,无内存碎片;效率高,只需复制存活对象
缺点:内存利用率低,复制成本高
整理算法
标记存活对象,将所有对象整理到内存一端,然后直接清除边界以外的所有垃圾
优点:无内存碎片,内存利用率高;
缺点:整理需要移动对象,成本高
分代收集算法
将对象分为年轻代和老年代,年轻代用复制算法回收,老年代用清除、整理来回收
JVM 垃圾收集器
垃圾回收器时垃圾回收算法的具体实现
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Myskill-blog!