14. 垃圾回收概述
14.1 什么是垃圾?
垃圾指运行程序中没有任何指针指向的对象。
如果不及时清理,那么这些垃圾就会占用内存直至应用结束,内存迟早会被消耗完
14.4 Java垃圾回收机制
- 自动内存管理。无序开发人员手动操作内存,避免了内存泄漏的风险,可以专心于业务开发
- 缺点是会弱化开发者对OOM的解决问题的能力
15. 垃圾回收相关算法
判断对象存活一般有两种方式:引用计数算法和可达性分析算法
15.1 垃圾标记阶段 - 引用计数算法
每个对象保存一个引用计数器属性,用于记录对象被引用的情况
- 优点:实现简单,便于辨识
- 缺点:计数器增加了时间开销与空间开销
致命弱点:无法处理循环引用,因此Java回收器里没有使用这类算法
15.2 垃圾标记阶段 - 可达性分析算法
简单,高效,有效解决循环引用的问题,Java选择这种算法
以GC Roots为起始点,从上到下搜索被根对象集合所连接的目标对象是否可达,这个链被称为引用链
没有引用链相连的对象即为不可达,可被标记为垃圾对象
- 注意:可达性分析算法是STW的重要原因之一
15.3 对象的finalization机制
Java提供了finalization机制允许开发人员对对象销毁之前自定义处理逻辑
垃圾回收此对象之前,总会先调用finalize()方法
finalize()方法允许在子类中被重写
注意:不要主动调用对象的finalize()方法!应该交给GC来调用!
- finalize()时可能导致对象复活
- finalize()方法的执行时间没有保证,完全由GC决定。要是不GC很容易卡那
- 糟糕的finalize()会严重影响GC性能
15.4 GC dump
GC dump是Java虚拟机在进行垃圾回收时生成的一份内存快照文件,用于分析程序运行时的内存使用情况。
使用GC dump可以帮助开发人员找出内存泄漏、内存溢出等问题,以及分析内存使用情况,优化程序性能。
GC dump的生成方式有多种,可以通过命令行参数、JMX控制台、调试工具等方式生成。一般情况下,建议使用jmap命令生成GC dump文件,具体命令如下:
jmap -dump:format=b,file=heapdump.bin
其中,format参数指定生成文件格式为二进制格式,file参数指定生成的文件名称,pid参数指定Java进程的进程ID。
生成GC dump文件后,可以使用各种分析工具,如MAT、VisualVM等工具对文件进行分析。
15.5 垃圾清除阶段 - 标记-清除算法
- 执行过程:堆中有效空间被耗尽的时候,就会STW然后进行标记与清除
- 标记:从根节点开始遍历,标记所有被引用的对象
- 清除:对堆内存从头到尾遍历。遍历到被标记的部分就将其回收
- 缺点:需要STW,用户体验差
- 这种方式清理出的内存空间不连续,产生内存碎片
15.6 垃圾清除阶段 - 标记-复制算法
将内存空间分为两块。每次使用一块,GC时将正在使用部分的存活对象复制至另一块中,再将本块所有对象清除即可。
- 优点:实现简单,运行高效,保证GC后空间连续性
- 缺点:需要两倍运行空间
- 新生代大量对象需要GC,大量清除少量复制,用本方法比较好。
15.7 垃圾清除阶段 - 标记-压缩算法
效果等同于一次标记-清除算法后,进行一次内存碎片整理
- 优点:优化了之前标记-清除算法的内存分散问题,也没有标记-复制算法的内存减半问题
- 缺点:效率低,调整引用地址有开销,STW时间长
15.8 小结
目前几乎所有GC算法都采用分代收集算法进行垃圾回收
16. 垃圾回收相关概念
16.1 System.gc()的理解
System.gc() 方法只是建议 JVM 执行垃圾回收,但具体是否执行还取决于 JVM 的实现和当前系统的资源情况。在某些情况下,JVM 可能会忽略这个建议。因此,开发者不应该依赖 System.gc() 方法来确保垃圾回收的执行。
16.2 内存溢出与内存泄漏
- 内存溢出:没有空闲内存,且GC也无法提供更多内存
- 内存泄漏:对象不会被程序用到了,但GC又不能回收它们的情况
16.3 Stop the world
指GC过程中应用程序的停顿。
STW由JVM在后台自动发起和完成。
所有GC都有这个事件,不可避免,只能尽可能缩短
16.4 安全点与安全区域
- 安全点:GC能够安全暂停程序并执行GC的时间点
- 安全区域:这段时间内,对象的引用关系不会发生变化,在这个区域内任何时间GC都安全。(相当于扩展的安全点)
16.5 强软弱虚引用
-
强引用:程序中最常见也是默认的引用。只要强引用的对象可触及,JVM宁肯报OOM也不会回收。
强引用可能导致内存泄漏 -
软引用:迫不得已在OOM之前才会尝试回收。
实现方式:java.lang.SoftReference -
弱引用:只要触发GC就回收。
但是GC线程优先级不高,所以弱引用的对象可能能活一段时间
实现方式:java.lang.WeakReference -
虚引用:跟没有一样。设置虚引用的唯一目的就是跟踪GC的过程(在这个对象被回收时会收到一个通知)
17. 垃圾回收器
17.1 分类
- 并行与串行
- 并发式与独占式:并发式指应用程序与GC交替进行;独占式指GC运行时会停止应用程序的运行
- 压缩式与非压缩式:GC后是否压缩内存碎片
- 按工作区间区分:新生代,老年代回收器
17.2 性能指标
- 吞吐量:用户代码运行时间占总时间的比例
- 暂停时间:GC时应用程序被暂停的时间
- 收集频率:GC发生的频率
- 内存占用:GC占用内存大小
- 快速
17.3 不同垃圾收集器概述
- 串行回收器:Serial,Serial Old
- 并行回收器:ParNew,Parallel Scavenge,Parallel Old
- 并发回收器:CMS ,G1
17.4 串行回收 - Serial/Serial Old
- 串行,Serial用于新生代,复制算法,STW机制;Serial Old用于老年代
- 简单高效但单核,现在不怎么用了
17.5 ParNew回收器
相当于Serial的并行版本
同样采用复制算法,STW机制,新生代回收,只是采用并行回收
多核情况下比Serial效果更好,但单核情况下会差
17.6 Parallel Scavenge 与 Parallel Old
- Parallel Scavenge
年轻代回收,复制算法,并行回收,STW机制,这些和ParNew一样
但Parallel Scavenge以吞吐量优先 - Parallel Old
老年代,并行回收,STW机制,标记-压缩算法。
两者组合使用,效果不错
Java8中的默认垃圾收集器
17.7 CMS收集器
老年代收集器,标记-清除算法,STW,真正意义下的并发收集器(用户线程与GC线程并发工作),尽可能减少停顿时间
CMS不能配合Parallel Scavage工作,所以新生代只能选择ParNew或Serial
CMS工作原理:四个阶段
- 初始标记阶段:STW,标记出GC Roots关联的对象,非常快
- 并发标记阶段:不STW,从GC Roots开始遍历整个对象图,时间较长但不停顿
- 重新标记阶段:修正并发标记期间变动的标记记录,停顿时间比1长但比2短
- 并发清除阶段:清理,并发运行
还是需要STW的,但整体停顿时间较低
CMS需要足够内存,所以不能等老年代满了才CMS GC,需要老年代到一定内存阈值就开始CMS
因为是标记-清除算法,所以会有内存碎片
以上GC回收器的选用:
17.8 G1回收器
全功能垃圾收集器,面向服务端应用,适应不断扩大的内存与CPU核数,JDK 9以后的默认垃圾回收器
侧重于延迟可控下获得较高吞吐量
特点:
- 并行与并发:G1回收器可利用多核并行执行,且G1与工作线程可以并发工作
- 分代收集:G1依然会区分老年代和新生代,但不是传统意义的分区了。
如下图所示:内存被划分成一个个Region。Region内部用复制算法,但整体的块可以看作标记-压缩算法。 - 停顿时间可预测
- 大内存下G1好用,小内存下CMS好用。平衡点在6-8G之间
G1对内存分块时新增了一种Humongous快,用于储存大对象
G1 GC过程:
17.9 垃圾回收器总结
截至Java 1.8