讲讲JVM的垃圾回收机制
垃圾回收是指JVM对内存中已经死亡的,不再使用的对象进行清除或回收。
常见的垃圾回收算法有标记-复制,标记-整理,标记-清除,分代收集算法等
一般的垃圾回收。过程是先使用可达性分析算法得出内存中哪些对象是不可达的,然后对存活对象进行标记,清除不可达对象,然后进行内存的压缩/整理。
如何判断对象是否存活?
可达性分析算法,通过一组名为GC root的根对象进行递归扫描,无法从根对象到达的对象就是垃圾。G1,GMS等主流垃圾回收器的算法。
什么是引用计数法?
每个对象都有一个引用计数器,代表有多少个引用指向它,当引用计数器为0时,可以回收。
引用计数器无法解决循环引用的问题,比如两个对象相互引用,但是没有别的对象指向他们,引用计数器也不为0,所以无法被回收。
做可达性分析前应该有哪些前置操作?
STW,Stop the World,会让所有线程在其安全点暂停,这样做是为了保证引用关系不会在垃圾回收的过程中被更改,从而导致内存错误和数据丢失
Java中可作为GC Roots的引用有哪几种
GC Roots必须是一组活跃的引用,他们是所有引用链的源头,常见的GC Roots有以下几种:
- 虚拟机栈中的引用(引用类型的方法参数、局部变量)
- 本地方法的JNI引用
- 类静态final变量
- 运行时常量池中的常量引用(String,Class)
说说虚拟机栈中的引用?
如果方法内部创建了一个引用类型的局部变量,指向一个对象,那么这个局部变量就是GC Root,其指向的对象可以从GC Root达到,方法结束后,局部变量的作用域结束,其之前指向的对象如果没有其他引用指向,就会被回收
说说本地方法栈中的JNI引用?
JNI引用就是Java本地方法中定义的指向Java对象的引用,它在本地方法执行期间保持 Java 对象活跃,可以被认为是 GC Roots。
一旦本地方法执行完毕,如果JNI引用不是全局的,那它之前指向的Java对象就会被回收(如果没有其他引用指向它)
说说类静态final变量?
静态变量属于类,定义在类中,其可以作为GC Root,其生命周期和类的生命周期一致,只要其所属的类不被垃圾回收(卸载),其就永远作为GC Root活跃,其指向的对象也永远可达。但如果其所属的类被垃圾回收(卸载),那么其生命周期也就结束,其指向的对象如果没有其他引用指向它,也会被垃圾回收。
说说运行时常量池中的常量引用?
class ConstantPoolReference {public static final String CONSTANT_STRING = "Hello, World"; // 常量,存在于运行时常量池中public static final Class<?> CONSTANT_CLASS = Object.class; // 类类型常量public static void main(String[] args) {System.out.println(CONSTANT_STRING);System.out.println(CONSTANT_CLASS.getName());}
}
运行时常量池中的常量引用有很多,比如字符串常量引用,类的直接引用(指向Class对象),static final修饰的常量的引用,这些都是GC Root。
以上代码中CONSTANT_STRING是一个常量(因为被static final修饰,而不是因为Hello World这个字符串对象本身在常量池中的常量引用),所以会被存在运行时常量池中,运行时常量池中的引用都是GC Root,所以其是一个GC Root。
CONSTAN_CLASS也是一个常量(因为被static final修饰,而不是因为Object这个class对象本身在常量池中的直接引用),所以会被存在运行时常量池中,运行时常量池中的引用都是GC Root,所以其是一个GC Root。
ConstantPoolReference类的运行时常量池:
├── CONSTANT_STRING常量引用 → "Hello, World"对象 (GC Root)
├── CONSTANT_CLASS常量引用 → Object.class对象 (GC Root)
├── "Hello, World"字符串字面量引用 → "Hello, World"对象 (GC Root)
├── Object.class直接引用 → Object.class对象 (GC Root)
└── ...注意:同一个对象被多个GC Root保护
这些常量引用的对象(字符串"Hello, World"和 Object.class 类对象)在常量池中,就算包含这些常量的 ConstantPoolReference 类未被卸载,这些对象也不会被垃圾回收。
finalize了解吗?
如果对象重写了finalize方法,并且JVM判断其不可达,则会进行第一次GC,放入finalization队列,并异步执行finalize方法(finalize方法中可以使对象重新变为可达)。第二次GC会真正回收对象(如果复活则不会回收,但是下次不会再执行finalize方法)
说说分代收集算法?
新生代中采用标记-复制算法,老年代中采用的是标记-整理算法
为什么要使用分代收集算法?
因为新生代中的对象生命周期短,存活下来的也就少了,使用标记-复制算法的成本较低。老年代中存活对象数量多,使用标记-整理算法减少对象移动的开销。
标记-复制算法过程中会不会停顿?
会STW。
标记过程会停顿是为了避免对象的引用关系在标记过程中不被修改。
复制过程中会停顿是为了避免对象的内容在复制过程中被修改。
什么是Mixed GC和Full GC?
Mixed GC 是 G1 垃圾收集器特有的一种 GC 类型,它在一次 GC 中同时清理年轻代和部分老年代。
Full GC 是最彻底的垃圾收集,涉及整个 Java 堆和方法区。它是最耗时的 GC,通常在 JVM 压力很大时发生。
什么时候触发Minor GC?
当Eden区空间不足时
什么时候触发Full GC?
进行Minor GC的时候,如果JVM发现老年代没有足够的空间存放本次从新生代晋升的对象的总大小,并且老年代的最大可用的连续内存空间大小 < 历次Minor GC中从新生代进入老年代的对象大小之和的平均值(空间分配担保),说明本次 Young GC 后升入老年代的对象大小,可能超过了老年代当前可用的内存空间,就会触发 Full GC。
System.gc()
、jmap -dump
等命令会触发 full gc。
知道哪些垃圾收集器?
垃圾收集器可以分为分代收集器和分区收集器。分代收集器的代表是CMS,分区收集器的代表是G1和ZGC。分代收集器就是将堆内存划分为多个大小相等的区域,根据各区域的垃圾回收价值动态选择回收策略,每个Region可以是Eden、Survivor、Old或Humongous
说说Serial垃圾收集器?
Serial是最基础的单线程垃圾收集器,进行垃圾收集时必须暂停其它所有线程的执行,仅剩一条垃圾回收线程执行(STW)
说说ParNew垃圾收集器?
是Serial的多线程版本,使用多条线程进行垃圾收集
说说Parallel Scavenge垃圾收集器?
是一款新生代收集器,基于标记-复制算法实现,类似于ParNew,也能并行收集,但主要关注的是垃圾收集的吞吐量,通过自动调整Eden和Survivor的大小和比例,以及晋升阈值,最大化应用程序运行时间的占比。
说说Serial Old垃圾回收器?
是一款老年代垃圾回收器,同样是单线程的,采用标记-整理算法
说说Parallel Old收集器?
Parallel Scavenge的老年代版本,是多线程的,基于标记-整理算法
说说CMS垃圾收集器?
低延迟的老年代垃圾处理器,采用标记-清除算法,分为初始标记、并发标记、重新标记、并发清理四个过程,垃圾回收线程可以和用户线程并行运行,停顿时间短,但是容易产生内存碎片。
- 初始标记:标记所有从GC Roots直接可达的对象为灰色,需要STW但是很快
- 并发标记:从初始标记的对象出发,遍历所有对象,标记所有灰色对象引用的对象为灰色,并将原来的所有灰色对象自身标记为黑色,是并发执行的
- 重新标记:完成剩余的标记工作,包括处理并发阶段留下来的少量变动,需要短暂的STW
- 处理写屏障记录的引用变化:在并发标记阶段,应用程序可能会更新对象的引用(比如一个黑色对象新引用了一个白色对象),这些变化会通过写屏障记录下来
- 循环扫描灰色对象:循环遍历灰色对象,将灰色对象设为黑色,再将所有可达的白色对象设为灰色,循环执行,确保引用的对象最终都被正确的标记为黑色
- 清理:确保所有引用关系正确处理后,灰色对象标记为黑色,不可达的白色对象保持不变。这一步完成后,所有存活对象都应当是黑色的。
- 并发清理:并发清理所有不可达的对象
三色标记法用于标记对象的存活状态,它将对象分为三类:
- 白色(White):尚未访问的对象。垃圾回收结束后,仍然为白色的对象会被认为是不可达的对象,可以回收。
- 灰色(Gray):已经访问到但未标记完其引用的对象。灰色对象是需要进一步处理的。
- 黑色(Black):已经访问到并且其所有引用对象都已经标记过。黑色对象是完全处理过的,不需要再处理。
说说G1垃圾收集器?
属于分区收集器,将堆划为为多个相同大小的Region,面向大内存、高吞吐场景,采用标记-整理算法,避免了内存碎片,优点是停顿时间可控,适合大堆场景,但是调优困难
老年代占用率达到阈值时,Mixed GC回收过程:
并发标记:G1垃圾收集器以并发标记的方式标出老年代中的存活对象
混合收集:并发标记完成后,G1会计算出哪些区域回收价值最高(包含最多垃圾),然后优先回收这些区域,提高回收效率和减少停顿时间,
可预测的停顿:用户可以设置自己预期的停顿时间,G1会尽量在这个时间内完成垃圾回收
说说ZGC垃圾收集器?
超低延迟不固定分区大小的垃圾回收器,通过并发标记和重定位来避免大部分STW操作,主要通过指针染色来管理对象状态。
- 并发标记对象的可达性:通过对指针上增加标记位判断对象的存活状态
- 重定位状态:对象被移动时,通过指针染色来更新对象的引用
CMS vs G1
特性 | CMS | G1 |
---|---|---|
设计目标 | 低停顿时间 | 可预测的停顿时间,优先回收机制 |
并发性 | 是 | 是 |
内存碎片 | 是,容易产生碎片 | 否,通过区域划分和压缩减少碎片 |
收集代数 | 老年代 | 整个堆,但区分年轻代和老年代 |
并发阶段 | 并发标记、并发清理 | 并发标记、并发清理、并发回收(移动) |
停顿时间预测 | 较难预测 | 可配置停顿时间目标 |
CMS 适用于对延迟敏感的应用场景,主要目标是减少停顿时间,但容易产生内存碎片。
G1 则提供了更好的停顿时间预测和内存压缩能力,适用于大内存和多核处理器环境。