赞
踩
【面试精讲】Java垃圾回收算法分析和代码示例
目录
二、可达性分析(Reachability Analysis)算法
六、分代收集(Generational Collection)算法
在深入探讨Java垃圾回收(GC)算法之前,了解Java内存管理的基本原理是至关重要的。Java虚拟机(JVM)通过垃圾回收过程自动管理内存,确保不再使用的对象能够被及时清理,从而避免内存泄漏。本文将详细介绍Java中常见的几种垃圾回收算法,并探讨它们的适用场景、优缺点以及工作原理。
它是指在创建对象时关联一个与之相对应的计数器,当此对象被使用时加 1,相反销毁时 -1。当此计数器为 0 时,则表示此对象未使用,可以被垃圾收集器回收。
引用计数算法的优缺点很明显,其优点是垃圾回收比较及时,实时性比较高,只要对象计数器为 0,则可以直接进行回收操作;而缺点是无法解决循环引用的问题,比如以下代码:
- class CustomOne {
- private CustomTwo two;
-
- public CustomTwo getCustomTwo() {
- return two;
- }
-
- public void setCustomTwo(CustomTwo two) {
- this.two = two;
- }
- }
-
- class CustomTwo {
- private CustomOne one;
- public CustomOne getCustomOne() {
- return one;
- }
-
- public void setCustomOne(CustomOne one) {
- this.one = one;
- }
- }
-
- public class RefCountingTest {
- public static void main(String[] args) {
- CustomOne one = new CustomOne();
- CustomTwo two = new CustomTwo();
- one.setCustomTwo(two);
- two.setCustomOne(one);
- one = null;
- two = null;
- }
- }
是目前商业系统中所采用的判断对象死亡的常用算法,它是指从对象的起点(GC Roots)开始向下搜索,如果对象到 GC Roots 没有任何引用链相连时,也就是说此对象到 GC Roots 不可达时,则表示此对象可以被垃圾回收器所回收,如下图所示:
工作原理:标记-清除算法是最基础的垃圾回收策略,其过程分为两个阶段:标记和清除。在标记阶段,算法遍历所有可达对象,并对这些对象进行标记;随后,在清除阶段,未被标记的对象,即不可达对象,将被视为垃圾并被回收。
优点:实现相对简单,不需要移动对象。
缺点:两大主要缺点是执行效率不高(特别是在堆内存大时)和造成内存碎片。
代码示例
由于JVM底层实现垃圾回收,我们平时编写的Java代码一般无需直接与垃圾回收算法打交道。因此,以下展示的并非真正执行垃圾回收的代码,而是为了帮助理解标记-清除算法的简化伪代码:
- // 伪代码,用于说明标记-清除算法
- void mark(Object root) {
- if (root == null || root.isMarked()) return;
- root.mark(); // 标记当前对象
- for (Object ref : root.getReferences()) {
- mark(ref); // 递归标记引用对象
- }
- }
-
- void sweep() {
- for (Object obj : heap) {
- if (!obj.isMarked()) {
- heap.remove(obj); // 清除未标记的对象
- } else {
- obj.unmark(); // 清除标记,为下次GC做准备
- }
- }
- }
工作原理:复制算法将内存空间一分为二,每次只使用其中的一个子空间。在垃圾回收时,将正在使用的内存区域中的活动对象复制到未使用的区域中,然后清除正在使用的内存区域中的所有对象,达到快速回收的目的。
优点:没有内存碎片问题,且可以快速完成内存回收。
缺点:内存利用率低,因为任何时候只有一半的内存区域可以被利用。
代码示例
- // 伪代码,用于说明复制算法
- void copy() {
- for (Object obj : currentArea) {
- if (obj.isAlive()) {
- moveTo(obj, anotherArea); // 将活动对象复制到另一个区域
- }
- }
- clear(currentArea); // 清理当前区域
- swapAreas(); // 交换当前区域和另一个区域的角色
- }
工作原理:标记-整理算法是标记-清除算法的改进版本。在完成标记活动对象之后,不是简单地清理未标记对象,而是将所有活动对象都向一端移动,然后直接清理边界外的内存。
优点:解决了标记-清除算法的内存碎片问题。
缺点:需要移动对象,增加了额外的开销。
代码示例
- // 伪代码,用于说明标记-整理算法
- void markCompact() {
- markAll(); // 标记所有活动对象
- compact(); // 将所有活动对象移动到内存区域的一端
- clearRest(); // 清理掉边界外的内存
- }
工作原理:分代收集算法基于这样一个事实:不同生命周期的对象应当采取不同的收集策略。Java堆分为新生代和老年代,新生代使用复制算法,老年代使用标记-整理或标记-清除算法。
优点:提高了垃圾回收的效率,尤其是对于那些生命周期短的对象。
缺点:实现复杂,需要更细致地管理内存区域。
代码示例
- // 伪代码,用于说明分代收集算法
- void generationalCollect() {
- if (minorGCCondition()) {
- minorGC(); // 在新生代执行复制算法或其他适合短生命周期对象的算法
- }
- if (majorGCCondition()) {
- majorGC(); // 在老年代执行标记-清除或标记-整理算法
- }
- }
当使用可达性分析判断一个对象不可达时,并不会直接标识这个对象为死亡状态,而是先将它标记为“待死亡”状态再进行一次校验。校验的内容就是此对象是否重写了 finalize() 方法,如果该对象重写了 finalize() 方法,那么这个对象将会被存入到 F-Queue 队列中,等待 JVM 的 Finalizer 线程去执行重写的 finalize() 方法,在这个方法中如果此对象将自己赋值给某个类变量时,则表示此对象已经被引用了。因此不能被标识为死亡状态,其他情况则会被标识为死亡状态。
- public class FinalizeTest {
- // 需要状态判断的对象
- public static FinalizeTest Hook = null;
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- System.out.println("执行了 finalize 方法");
- FinalizeTest.Hook = this;
- }
-
- public static void main(String[] args) throws InterruptedException {
- Hook = new FinalizeTest();
- // 卸载对象,第一次执行 finalize()
- Hook = null;
- System.gc();
- Thread.sleep(500); // 等待 finalize() 执行
- if (Hook != null) {
- System.out.println("存活状态");
- } else {
- System.out.println("死亡状态");
- }
-
- // 卸载对象,与上一次代码完全相同
- Hook = null;
- System.gc();
- Thread.sleep(500); // 等待 finalize() 执行
-
- if (Hook != null) {
- System.out.println("存活状态");
- } else {
- System.out.println("死亡状态");
- }
- }
- }
垃圾回收是Java内存管理中不可或缺的一部分,不同的垃圾回收算法各有优劣,适用于不同的场景和需求。现代JVM如HotSpot,提供了多种垃圾回收器,比如Serial、Parallel、CMS、G1等,它们背后的实现原理基本上是上述垃圾回收算法的变体或组合。理解这些算法的基本原理,可以帮助开发者更好地理解JVM的内存管理机制,从而写出更高效、更稳定的Java应用程序。
如果本文对你有帮助 欢迎 关注 、点赞 、收藏 、评论, 博主才有动力持续记录遇到的问题!!!
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。