文章目录
JVM 垃圾回收机制
概述
- 在内存中已经不再被使用到的内存空间就是垃圾。
垃圾标记阶段(判断对象存活)
垃圾标记阶段用于判断对象是否可回收,只有被标记为已死亡的对象,GC才会在执行垃圾回收时,释放其占用的内存空间。
在Java虚拟机中,判断对象是否存活的两种方式:
- 引用计数法
- 可达性分析法
引用计数法
说明:
- 在对象中添加一个引用计数器。
- 当对象被引用+1,当对象失去引用-1;引用计数为0时,对象不再被引用,可以被回收。
优点:
- 简单
- 高效。
缺点:
- 需要单独的字段存储计数器,增加了内存开销;每次赋值需要更新计数器,增加了时间开销。
- 无法解决循环引用问题,目前在Java中几乎不使用这种算法。
objA.name = objB;
objB.name = objA;
可达性分析法
可达性分析法是以 GC Roots 为起点,从这些节点开始向下搜索,搜索所走过的路径被称为引用链,当一个对象没有被任何引用链相连时,则证明该对象是不可用的,表示可以被回收。
GC Roots 说明:
GC Roots 是一组活跃的引用。GC Roots 对象包含:
- 虚拟机栈(栈帧中的局部变量表)中引用的对象
- 方法区中的静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
- 同步锁synchronized持有的对象
垃圾回收阶段
当区分出内存中存活对象和死亡对象后,GC接下来的任务是执行垃圾回收,释放掉死亡对象所占用的内存空间。
标记-清除算法
算法过程:
该算法分标记和清除两个阶段:
- 标记阶段:标记所有需要回收的对象。
- 清除阶段:统一回收所有被标记的对象。
优点:
- 算法简单、实现简单。
缺点:
- 效率问题:标记和清除这两个过程效率不高。大部分对象都是朝生夕死的,因此需要大量标记对象和回收对象。
- 空间问题:标记-清除后,会产生大量的内存碎片。内存碎片太多可能会导致需要分配较大对象时,无法找到足够的连续空间,从而提前触发另一次垃圾收集动作。
场景:
- 标记-清除算法适合老年代。
复制算法
算法过程:
- 将内存分为大小相等的两块,每次只使用其中一块。
- 当这一块内存用完了,将这块内存中存活的对象复制到另一块中,然后一次清除使用的那块内存。
优点:
- 效率比标记-清除算法高。
- 不会产生内存碎片问题。
缺点:
- 可使用的内存缩小为原来的一半。
- 当存活对象较多时,需要做多次复制操作,效率将变低。
场景:
- 复制算法适合新生代。
标记-整理算法
与标记-清除算法类似,多了一个中间操作:整理内存。
标记-清除算法是一种非移动式的回收算法,标记-整理是移动式的。
算法过程:
- 标记:标记存活对象。
- 整理:让所有存活的对象向一端移动。
- 清除:统一清除端以外的对象。
优点:
- 一次清除端外区域,比标记-清除算法中的清除效率高。
- 不会产生内存碎片。
缺点:
- 移动对象时会触发STW。
场景:
- 标记-整理算法适合老年代。
分代收集算法
现在主流的虚拟机基本都采用 分代收集算法 ,即根据不同区域特点选择不同垃圾收集算法。
- 根据对象存活周期不同,堆内存分为:新生代和老年代。
- 新生代占1/3空间。
- Eden区占8/10
- From区占1/10
- To区占1/10
- 老年代占2/3空间。
- 新生代占1/3空间。
- 根据特点选择对应的垃圾收集算法。
- 新生代:对象存活率低,垃圾回收行为频率高,采用复制算法,使用Minor GC。
- 老年代:对象存活率搞,垃圾回收行为频率低,采用标记-整理和标记-清除算法,使用Full GC。
算法过程:
- 新创建的对象会先放在Eden区(位于新生代区,该区有大小限制),如果是大对象(如很长的字符串、数组)会直接分配到老年代区,避免频繁的内存复制。
- 如果Eden区的空间占满,程序又需要创建对象时,会触发MinorGC,将Eden区的存活对象移动到S0区并销毁垃圾对象,新创建的对象会放在Eden区,这是第一轮MinorGC后的操作。
- 当发生第二轮MinorGC后,会将存活Eden区和S0区的存活对象复制到S1区,并清除Eden区和S0区。
- 每次MinorGC存活对象的年龄都会加1,当存活对象的年龄达到阀值(默认为15),存活对象会移动到老年代区。
- 当老年代区内存不足时,会触发MajorGC,对老年代和新生代进行回收。当老年代内存仍不足时,会产生OOM异常。
优点:
- 效率高。
- 空间利用率高。
垃圾收集行为
- Minor GC(小型垃圾收集):Minor GC主要清理新生代(Young Generation)的内存空间。新生代通常分为Eden区和两个Survivor区(S0和S1),大部分新创建的对象首先被分配到Eden区,当Eden区满时,就会触发Minor GC。
- Major GC(大型垃圾收集):Major GC主要清理老年代(Old Generation)的内存空间。当对象在新生代存活一段时间后,或者Survivor区无法容纳的对象,会被移动到老年代。当老年代空间不足时,就会触发Major GC。Major GC的开销通常比Minor GC大,因为它涉及到整个堆的清理。
- Full GC(全量垃圾收集):Full GC涉及到整个堆(包括新生代和老年代)以及方法区的清理。Full GC的开销最大,因为它需要暂停所有的应用线程(Stop-The-World)来进行垃圾收集。
Stop-the-World
Stop-the-World,简称STW,当 Full GC 发生时,会产生应用程序的停顿。停顿产生时整个应用程序都会被暂停,没有任何响应,像卡死一样,这个停顿称为STW。被STW中断的应用程序线程会在完成GC之后恢复。
垃圾收集器类型
查看垃圾收集器种类指令:java -XX:+PrintCommandLineFlags -version
收集器 | 收集对象和算法 | 集器类型 | 说明 | 适用场景 |
---|---|---|---|---|
Serial | 新生代,复制算法 | 单线程 | 简单高效;适合内存不大的情况; | |
ParNew | 新生代,复制算法 | 并行的多线程收集器 | ParNew垃圾收集器是Serial收集器的多线程版本 | 搭配CMS垃圾回收器的首选 |
Parallel Scavenge吞吐量优先收集器 | 新生代,复制算法 | 并行的多线程收集器 | 类似ParNew,更加关注吞吐量,达到一个可控制的吞吐量; | 本身是Server级别多CPU机器上的默认GC方式,主要适合后台运算不需要太多交互的任务; |
Serial Old | 老年代,标记整理算法 | 单线程 | Client模式下虚拟机使用 | |
Parallel Old | 老年代,标记整理算法 | 并行的多线程收集器 | Parallel Scavenge收集器的老年代版本,为了配合Parallel Scavenge的面向吞吐量的特性而开发的对应组合; | 在注重吞吐量以及CPU资源敏感的场合采用 |
CMS | 老年代,标记清除算法 | 并行与并发收集器 | 尽可能的缩短垃圾收集时用户线程停止时间;缺点在于: 1.内存碎片 2.需要更多cpu资源 3.浮动垃圾问题,需要更大的堆空间 | 重视服务的响应速度、系统停顿时间和用户体验的互联网网站或者B/S系统。互联网后端目前cms是主流的垃圾回收器; |
G1 | 跨新生代和老年代;标记整理 + 化整为零 | 并行与并发收集器 | JDK1.7才正式引入,采用分区回收的思维,基本不牺牲吞吐量的前提下完成低停顿的内存回收;可预测的停顿是其最大的优势; | 面向服务端应用的垃圾回收器,目标为取代CMS |