空间分配担保
JDK 6 Update 24 之前:
在触发Minor GC
之前,虚拟机会先检查老年代最大连续可用空间是否大于新生代所有对象总空间:
- 如果大于,则这次Minor GC可以确保是安全的
- 否则会判断
-XX:HandlePromotionFailure
参数是否允许担保失败- 如果允许,则检查老年代最大连续可用空间是否大于历次晋升到老年代对象的平均大小
- 如果大于,则冒险进行一次
Minor GC
- 否则出发
Full GC
- 如果大于,则冒险进行一次
- 不允许则触发
Full GC
- 如果允许,则检查老年代最大连续可用空间是否大于历次晋升到老年代对象的平均大小
JDK 6 Update 24 之后:只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行Minor GC
,否则将进行Full GC
如何判断对象应该被回收
引用计数法
原理:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数为零的对象就是不可能再被使用的。
缺陷:对象之间相互循环引用
1 | public class ReferenceCountingGC { |
主流的Java虚拟机都没有选用引用计数法
可达性分析法
当前主流的商用程序语言(Java,C#等)都是通过可达性分析算法来判定对象是否存活。
原理:通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,则证明此对象不可能再被使用的。
GC Roots:
- 虚拟机栈中引用的对象
- 本地方法栈中引用的对象
- 类静态属性引用的对象
- 常量引用的对象
- 所有被同步锁(synchronized关键字)持有的对象
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointerException、OutOfMemoryError)等,还有系统类加载器
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
对象不可达一定会被回收吗?
对象回收前,如果它的finalize()
方法被重写并且从未执行过,会先执行finalize()
方法,执行完后如果仍旧没有任何引用,那么该对象会被回收。
引用的类型
在JDK 1.2之后,Java对引用的概念进行了扩充:
- 强引用(Strongly reference):最传统的“引用”的定义,程序代码中普遍存在的引用赋值,即类似“Object obj=new Object()”,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象
- 软引用(Soft reference):用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在JVM内存不足时,就会被回收。可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。JDK1.2之后提供了SoftReference类来实现软引用
- 弱引用(Weak reference):用来描述非必须对象,强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后提供了WeakReference类来实现弱引用
- 虚引用(Phantom reference):也称为“幽灵引用”或者“幻影引用”,最弱的一种引用关系。一个对象是否有虚引用完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。JDK1.2之后提供了PhantomReference类来实现虚引用
垃圾回收算法
标记-清除算法
复制算法
标记-整理算法
分代收集算法
垃圾收集器
默认垃圾收集器:
JDK 8: Parallel Scavenge(新生代) + Parallel Old(老年代)
JDK 9-: G1(新生代 + 老年代)
![image-20241216103245314](/Users/effy/Library/Application Support/typora-user-images/image-20241216103245314.png)
Serial+ Serial Old
算法:新生代采用标记+复制算法,老年代采用标记+整理算法
缺点:
- 单线程,停顿时间(STW)长
![image-20241213170915843](/Users/effy/Library/Application Support/typora-user-images/image-20241213170915843.png)
ParNew + CMS
ParNew | CMS(Concurrent Mark Sweep) | |
---|---|---|
JVM参数 | -XX:+UseParNewGC | -XX:+UseConcMarkSweepGC |
特点 | 本质上是对Serial在多CPU下的优化,使用多线程回收 | 关注系统的暂停时间,允许用户线程和垃圾回收线程在某些步骤中同时执行,减少了用户线程的等待时间,吞吐量方面会下降 |
回收区域 | 新生代 | 老年代 |
回收算法 | 标记-复制算法 | 标记-整理算法 |
优点 | 多CPU下停顿时间较短 | 系统由于垃圾回收的停顿时间较短,用户体验好 |
缺点 | 吞吐量和停顿时间不如G1,JDK9以后不建议使用 | 1.内存碎片问题:使用标记-清除算法,存在内存碎片 2.退化问题:老年代内存不足分配对象,CMS会退化成Serial Old单线程回收老年代 3.浮动垃圾问题:无法处理并发清除过程中产生的浮动垃圾 |
适用场景 | JDK8及以前,和CMS搭配使用 | 大型互联网系统中用户请求数据量大、频率高的场景,比如订单接口、商品接口等 |
CMS执行步骤:
- 初始标记:用极短的时间标记出GC Roots直接关联的对象
- 并发标记:标记所有对象,用户线程不需要暂停
- 重新标记:由于并发标记阶段有些对象发生了变化,存在错标、漏标等情况,需要重新标记
- 并发清除:清理死亡的对象,用户线程不需要暂停
Parallel Scavenge + Parallel Old
Parallel Scavenge | Parallel Old | |
---|---|---|
JVM参数 | -XX:UseParallelGC -XX:MaxGCPauseMillis: 最大暂停时间 -XX:GCTimeRation: 吞吐量 -XX:UseAdaptiveSizePolicy: 自动调整内存大小 |
-XX:UseParallelOldGC |
特点 | 多线程并行回收,关注系统的吞吐量,具备自动调整堆内存大小的能力,允许用户设置最大暂停时间 | Parallel Scavenge的老年代版本,多线程并发收集, |
回收区域 | 新生代 | 老年代 |
算法 | 标记-复制算法 | 标记-整理算法 |
优点 | 吞吐量高、而且手动可控;为了调整吞吐量,虚拟机会动态调整堆的参数 | 并发效率高,在多核CPU下收集效率高 |
缺点 | 不能保证单次的停顿时间 | 暂停时间比较长长 |
适用场景 | 后台任务,不需要与用户交互,并且容易产生大量的对象。比如大数据的处理,大文件的导出 | 与Parallel Scavenge配套使用 |
工作流程 |
G1
G1的整个堆会被划分为多个大小相等的区域,称之为区Region,区域不要求是连续的。分为Eden、Survivor和Old区,以及存放大对象的Humogous区(超过一个Region区的1/2会被直接放在该区)
![image-20241216103646557](/Users/effy/Library/Application Support/typora-user-images/image-20241216103646557.png)
优点
- 支持巨大的堆空间回收,并有较高的吞吐量
- 支持多CPU并行垃圾回收
- 允许用户设置最大暂停时间
![image-20241216102322973](/Users/effy/Library/Application Support/typora-user-images/image-20241216102322973.png)