2026/5/23 20:45:51
网站建设
项目流程
自己做网站卖东西可以,网站公司深圳,2023新闻摘抄,杭州建设信息港一、垃圾回收思想垃圾回收的基本思想是考察每一个对象的可触及性#xff0c;即从根节点开始是否可以访问到这个对象#xff0c;如果可以#xff0c;则说明当前对象正在被使用#xff0c;如果从所有的根节点都无法访问到某个对象#xff0c;说明对象已经不再使用了#xf…一、垃圾回收思想垃圾回收的基本思想是考察每一个对象的可触及性即从根节点开始是否可以访问到这个对象如果可以则说明当前对象正在被使用如果从所有的根节点都无法访问到某个对象说明对象已经不再使用了一般来说此对象需要被回收。二、垃圾回收算法1、引用计数法实现对于一个对象A只要有任何一个对象引用了A则A的引用计数器就加1当引用失效时引用计数器就减1。只要对象A的引用计数器的值为0则对象A就不可能再被使用。引用计数器的实现也非常简单只需要为每个对象配备一 个整型的计数器即可。存在问题1.无法处理循环引用的情况。因此在Java的垃圾回收器中没有使用这种算法。2.引用计算器要求在每次因引用产生和消除的时候需要伴随一个加法操作和减法操作对系统性能会有一定的影响。案例描述一个简单的循环引用问题描述如下:有对象A和对象B, 对象A中含有对象B 的引用对象B中含有对象A的引用。此时对象A和B的引用计数器都不为0。但是在系统中却不存在任何第3个对象引用了A或B。也就是说A和B是应该被回收的垃圾对象但由于垃圾对象间相互引用从而使垃圾回收器无法识别引起内存泄漏。如图所示不可达的对象出现循环引用它的引用计数器均不为0。代码实现import java.util.ArrayList; import java.util.List; /** * JVM引用计数法 * 注这是逻辑模拟真实JVM并未采用引用计数算法因循环引用缺陷 */ public class RefCountAlgorithmDemo { // 1. 定义带引用计数器的对象核心引用计数对象标识 static class RefCountObject { private int id; // 对象唯一标识 private int refCount; // 引用计数器核心 private RefCountObject refObj; // 指向其他对象的引用用于模拟循环引用 private boolean isRecycled; // 是否已被回收仅用于演示 public RefCountObject(int id) { this.id id; this.refCount 0; // 初始引用计数为0 this.isRecycled false; } // 引用计数器1有新引用指向当前对象时调用 public void increaseRef() { this.refCount; System.out.println(对象 id 引用计数1当前计数 this.refCount); } // 引用计数器-1某个引用失效时调用 public void decreaseRef() { if (this.refCount 0) { this.refCount--; System.out.println(对象 id 引用计数-1当前计数 this.refCount); } else { System.out.println(对象 id 引用计数已为0无需递减); } } // 回收当前对象模拟GC回收操作 public void recycle() { if (this.refCount 0 !isRecycled) { this.isRecycled true; this.refObj null; // 清空引用辅助回收 System.out.println(对象 id 引用计数为0已被回收); } else if (this.refCount 0) { System.out.println(对象 id 引用计数 this.refCount 无法回收); } } // 省略getter/setter public int getId() { return id; } public int getRefCount() { return refCount; } public RefCountObject getRefObj() { return refObj; } public void setRefObj(RefCountObject refObj) { this.refObj refObj; } public boolean isRecycled() { return isRecycled; } } // 2. 引用计数法GC管理器负责检测垃圾、回收垃圾 static class RefCountGC { // 模拟JVM堆内存存储所有创建的对象 private final ListRefCountObject heap new ArrayList(); // 向堆中添加对象模拟对象创建 public RefCountObject createObject(int id) { RefCountObject obj new RefCountObject(id); heap.add(obj); System.out.println(对象 id 已创建并加入堆内存); return obj; } // 建立引用关系obj1引用obj2需更新obj2的引用计数 public void setReference(RefCountObject obj1, RefCountObject obj2) { if (obj1 null || obj2 null) return; // 先清空obj1原有引用如果有避免计数错误 if (obj1.getRefObj() ! null) { RefCountObject oldRef obj1.getRefObj(); oldRef.decreaseRef(); // 原有引用失效计数-1 } // 建立新引用obj2计数1 obj1.setRefObj(obj2); obj2.increaseRef(); System.out.println(对象 obj1.getId() 引用了 对象 obj2.getId()); } // 释放引用变量不再指向对象需更新对象计数 public void releaseReference(RefCountObject obj) { if (obj null) return; obj.decreaseRef(); } // 执行GC遍历堆回收所有计数为0的对象 public void doGC() { System.out.println(\n 开始执行引用计数法GC ); for (RefCountObject obj : heap) { obj.recycle(); } // 清理堆中已回收的对象模拟内存释放 heap.removeIf(RefCountObject::isRecycled); System.out.println(GC执行完成堆中剩余对象数 heap.size() \n); } } // 测试主方法演示普通引用循环引用场景 public static void main(String[] args) { RefCountGC gc new RefCountGC(); // 场景1普通引用无循环引用计数法正常工作 System.out.println( 场景1普通引用 ); // 1. 创建对象1变量a指向它引用计数1 RefCountObject obj1 gc.createObject(1); gc.releaseReference(obj1); // 变量a指向obj1计数1不这里修正create后变量引用要手动加计数 obj1.increaseRef(); // 模拟变量a引用obj1计数1 // 2. 变量a置null引用失效计数-1 gc.releaseReference(obj1); obj1 null; // 3. 执行GC回收计数为0的obj1 gc.doGC(); // 场景2循环引用引用计数法的致命缺陷 System.out.println( 场景2循环引用 ); // 1. 创建对象2、3 RefCountObject obj2 gc.createObject(2); RefCountObject obj3 gc.createObject(3); // 2. 建立循环引用obj2引用obj3obj3引用obj2 gc.setReference(obj2, obj3); // obj2→obj3obj3计数1 gc.setReference(obj3, obj2); // obj3→obj2obj2计数1 // 3. 释放外部所有引用变量obj2、obj3置null gc.releaseReference(obj2); // obj2计数-1从1→0不先看setReference后的计数 // obj2被obj3引用计数1obj3被obj2引用计数1 gc.releaseReference(obj3); obj2 null; obj3 null; // 4. 执行GCobj2和obj3计数仍为1循环引用无法被回收 gc.doGC(); // 查看堆中剩余对象obj2、obj3仍在内存泄漏 System.out.println(循环引用场景后堆中剩余对象); for (RefCountObject obj : gc.heap) { System.out.println(对象 obj.getId() 引用计数 obj.getRefCount() 是否回收 obj.isRecycled()); } } }2、标记清除算法实现标记清除算法是现代垃圾回收算法的思想基础。标记清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是在标记阶段首先通过根节点标记所有从根节点开始的可达对象。因此未被标记的对象就是未被引用的垃圾对象。然后在清除阶段清除所有未被标记的对象。标记清除算法可能产生的最大问题是空间碎片。存在问题回收后的空间是不连续的。在对象的堆空间分配过程中尤其是大对象的内存分配不连续内存空间的工作效率要低于连续的空间。因此这也是该算法的最大缺点。案例描述如图所示使用标记清除算法对一块连续的内存空间进行回收。从根节点开始(这里显示了2个根)所有的有引用关系的对象均被标记为存活对象(箭头表示引用)。从根节点起不可达的对象均为垃圾对象。在标记操作完成后系统回收所有不可达的空间。代码实现import java.util.*; /** * JVM标记-清除算法Mark-Sweep * 注聚焦逻辑模拟重点体现“标记存活对象→清除垃圾→产生内存碎片”的核心特征 */ public class MarkSweepAlgorithmDemo { // 1. 模拟JVM内存中的对象含唯一ID、存活标记、内存地址 // 内存地址用数字模拟如100、200、300体现内存碎片问题 static class JvmObject { private int id; // 对象唯一标识 private int memoryAddr; // 模拟内存地址连续分配清除后会断档 private boolean isMarked; // 存活标记标记阶段赋值 private ListJvmObject refs; // 当前对象引用的其他对象模拟引用链 public JvmObject(int id, int memoryAddr) { this.id id; this.memoryAddr memoryAddr; this.isMarked false; // 初始无标记默认待判定 this.refs new ArrayList(); } // 添加引用模拟对象间的引用关系 public void addReference(JvmObject obj) { this.refs.add(obj); } // 重置标记GC完成后重置为下次GC做准备 public void resetMark() { this.isMarked false; } // 省略getter/setter public int getId() { return id; } public int getMemoryAddr() { return memoryAddr; } public boolean isMarked() { return isMarked; } public void setMarked(boolean marked) { isMarked marked; } public ListJvmObject getRefs() { return refs; } Override public String toString() { return Object{id id , 内存地址 memoryAddr , 存活标记 isMarked }; } } // 2. 标记-清除算法GC管理器核心标记、清除、内存管理 static class MarkSweepGC { // 模拟JVM堆内存存储所有对象用Map内存地址, 对象体现内存分布 private final MapInteger, JvmObject heapMemory new TreeMap(); // GC Roots模拟根节点如静态变量、栈引用是标记阶段的起点 private final SetJvmObject gcRoots new HashSet(); // 内存地址自增器模拟连续分配内存 private int nextMemoryAddr 100; // 初始内存地址从100开始 // 1. 分配对象模拟JVM给新对象分配内存 public JvmObject allocateObject(int id) { // 分配连续内存地址每次100模拟固定大小的对象内存块 int addr nextMemoryAddr; nextMemoryAddr 100; JvmObject obj new JvmObject(id, addr); heapMemory.put(addr, obj); System.out.println(分配对象 obj); return obj; } // 2. 将对象加入GC Roots模拟栈引用/静态变量引用 public void addToGcRoots(JvmObject obj) { if (obj ! null) { gcRoots.add(obj); System.out.println(对象 obj.getId() 加入GC Roots); } } // 3. 标记阶段核心从GC Roots出发递归标记所有可达对象 private void mark() { System.out.println(\n 开始标记阶段 ); // 遍历所有GC Roots标记可达对象 for (JvmObject rootObj : gcRoots) { // 递归标记当前对象及它引用的所有对象 markRecursive(rootObj); } // 打印标记结果 System.out.println(标记完成存活对象); heapMemory.values().stream() .filter(JvmObject::isMarked) .forEach(obj - System.out.println( obj)); } // 递归标记可达对象核心深度优先遍历引用链 private void markRecursive(JvmObject obj) { if (obj null || obj.isMarked()) { return; // 已标记或对象为空直接返回 } // 给当前对象打存活标记 obj.setMarked(true); System.out.println( 标记存活对象 obj); // 递归标记当前对象引用的所有对象 for (JvmObject refObj : obj.getRefs()) { markRecursive(refObj); } } // 4. 清除阶段核心清理未标记的垃圾对象释放内存 private void sweep() { System.out.println(\n 开始清除阶段 ); // 遍历堆内存收集所有未标记的垃圾对象地址 ListInteger garbageAddrs new ArrayList(); for (Map.EntryInteger, JvmObject entry : heapMemory.entrySet()) { JvmObject obj entry.getValue(); if (!obj.isMarked()) { garbageAddrs.add(entry.getKey()); System.out.println( 发现垃圾对象 obj 准备清除); } } // 清除垃圾对象释放内存 for (int addr : garbageAddrs) { heapMemory.remove(addr); } System.out.println(清除完成共清理 garbageAddrs.size() 个垃圾对象); // 重置所有存活对象的标记为下次GC做准备 heapMemory.values().forEach(JvmObject::resetMark); } // 5. 执行GC标记 → 清除 public void doGC() { System.out.println(\n 触发GC ); System.out.println(GC前堆内存对象分布 heapMemory.values()); // 第一步标记存活对象 mark(); // 第二步清除垃圾对象 sweep(); System.out.println(GC后堆内存对象分布 heapMemory.values()); // 打印内存碎片验证算法缺陷 printMemoryFragment(); } // 打印内存碎片核心查看内存地址是否连续 private void printMemoryFragment() { System.out.println(\n 内存碎片分析 ); if (heapMemory.isEmpty()) { System.out.println( 堆内存为空无碎片); return; } // 提取存活对象的内存地址并排序 ListInteger addrs new ArrayList(heapMemory.keySet()); Collections.sort(addrs); System.out.println( 存活对象内存地址 addrs); // 检测碎片地址是否连续本demo中对象内存块固定100地址差应为100 ListString fragments new ArrayList(); for (int i 1; i addrs.size(); i) { int prevAddr addrs.get(i - 1); int currAddr addrs.get(i); if (currAddr - prevAddr 100) { fragments.add(prevAddr ~ currAddr 之间存在碎片空闲内存 (currAddr - prevAddr - 100) ); } } if (fragments.isEmpty()) { System.out.println( 内存地址连续无碎片); } else { System.out.println( 发现内存碎片); fragments.forEach(f - System.out.println( f)); } } // 获取GC Roots测试用 public SetJvmObject getGcRoots() { return gcRoots; } } // 测试主方法演示标记-清除流程 内存碎片产生 public static void main(String[] args) { MarkSweepGC gc new MarkSweepGC(); // 1. 分配对象模拟引用关系 // 对象1内存地址100加入GC Roots栈引用 JvmObject obj1 gc.allocateObject(1); gc.addToGcRoots(obj1); // 对象2内存地址200被obj1引用 JvmObject obj2 gc.allocateObject(2); obj1.addReference(obj2); // 对象3内存地址300被obj2引用 JvmObject obj3 gc.allocateObject(3); obj2.addReference(obj3); // 对象4内存地址400无任何引用垃圾 JvmObject obj4 gc.allocateObject(4); // 对象5内存地址500被obj3引用 JvmObject obj5 gc.allocateObject(5); obj3.addReference(obj5); // 对象6内存地址600无任何引用垃圾 JvmObject obj6 gc.allocateObject(6); // 2. 触发第一次GC清理无引用的obj4、obj6产生内存碎片 gc.doGC(); // 3. 模拟后续分配大对象验证碎片影响 System.out.println(\n 尝试分配需要200连续内存的大对象 ); // 存活对象地址100、200、300、500 → 最大连续空闲内存为100如400、600无法分配200 System.out.println( 存活对象最大连续空闲内存100大对象需要200 → 分配失败内存碎片导致); } }3、复制算法实现复制算法的核心思想是:将原有的内存空间分为两块每次只使用其中一块在垃圾回收时将正在使用的内存中的存活对象复制到未使用的内存块中之后清除正在使用的内存块中的所有对象交换两个内存的角色完成垃圾回收。优点1、如果系统中的垃圾对象很多复制算法需要复制的存活对象数量就会相对较少。因此在真正需要垃圾回收的时刻复制算法的效率是很高的。2、又由于对象是在垃圾回收过程中统一被复制到新的内存空间中的因此可确保回收后的内存空间是没有碎片的。存在问题虽然有以上两大优点但是复制算法的代价却是将系统内存折半因此单纯的复制算法也很难让人接受。案例描述如图所示A.B两块相同的内存空间A在进行垃圾回收时将存活对象复制到B中,B中的空间在复制后保持连续。复制完成后清空A。并将空间B设置为当前使用空间。在Java的新生代串行垃圾回收器中使用了复制算法的思想。新生代分为eden空间、from空间和to空间3个部分。其中from和to空间可以视为用于复制的两块大小相同、地位相等、且可进行角色互换的空间块。from和to空间也称为survivor空间即幸存者空间用于存放未被回收的对象。如图所示。在垃圾回收时eden空间中的存活对象会被复制到未使用的survivor空间中(假设是to)正在使用的survivor空间(假设是from)中的年轻对象也会被复制到to空间中(大对象或者老年对象会直接进入老年代如果to空间已满则对象也会直接进入老年代)。此时eden 空间和from空间中的剩余对象就是垃圾对象可以直接清空to空间则存放此次回收后的存活对象。这种改进的复制算法既保证了空间的连续性又避免了大量的内存空间浪费。如图所示显示了复制算法的实际回收过程。当所有存活对象都复制到survivor区后(图中为to)简单地清空eden区和备用的survivor区(图中为from)即可。注意:复制算法比较适用于新生代。因为在新生代垃圾对象通常会多于存活对象。复制算法的效果会比较好。代码实现import java.util.ArrayList; import java.util.List; /** * 复制算法 */ public class CopyingGCDemo { // 模拟内存两个大小相等的空间 private static final int MEMORY_SIZE 10; private Object[] fromSpace new Object[MEMORY_SIZE]; private Object[] toSpace new Object[MEMORY_SIZE]; // 分配指针 private int fromAllocPtr 0; private int toAllocPtr 0; // 核心复制与收集 public void collect() { // 1. 交换空间角色 swapSpaces(); // 2. 重置目标空间分配指针 toAllocPtr 0; // 3. 从GC Roots开始复制此处简化Roots为一些已知对象 for (Object root : findGCRoots()) { if (root ! null) { copy(root); } } // 4. 复制完成后原From空间现在是垃圾可被整体视为清空 // (在实际JVM中只是移动指针并非真的置null) for (int i 0; i MEMORY_SIZE; i) { fromSpace[i] null; } // 5. 交换后当前toSpace变成了新的可用fromSpace fromAllocPtr toAllocPtr; // 更新分配指针到已用位置之后 } // 复制对象递归复制其引用的对象 private void copy(Object obj) { if (obj null || isForwarded(obj)) { return; } // 将对象复制到toSpace的新位置 int newAddr toAllocPtr; toSpace[newAddr] obj; // 在原位置留下转发指针这里用特殊标记模拟 forward(obj, newAddr); // 递归复制这个对象所引用的所有子对象 for (Object child : getReferences(obj)) { copy(child); } } // --- 以下为模拟辅助方法实际JVM中极其复杂 --- private ListObject findGCRoots() { // 模拟从栈、静态变量等找到的根对象 return new ArrayList(); } private boolean isForwarded(Object obj) { // 检查对象是否已被复制即是否有转发地址 return false; } private void forward(Object obj, int newAddr) { // 在对象头或旁边记录转发地址 } private ListObject getReferences(Object obj) { // 获取一个Java对象内部的所有引用字段 return new ArrayList(); } private void swapSpaces() { Object[] temp fromSpace; fromSpace toSpace; toSpace temp; int tempPtr fromAllocPtr; fromAllocPtr toAllocPtr; toAllocPtr tempPtr; } }4、标记压缩法实现复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。这种情况在新生代经常发生。但是在老年代更常见的情况是大部分对象都是存活对象。如果依然使用复制算法由于存活对象较多复制的成本也将很高。因此基于老年代垃圾回收的特性需要使用其他的算法。标记压缩算法是一种老年代的回收算法。它在标记清除算法的基础上做了一些优化。和标记清除算法一样标记压缩算法也首先需要从根节点开始对所有可达对象做一次标记。但之后它并不只是简单地清理未标记的对象而是将所有的存活对象压缩到内存的一端。之后清理边界外所有的空间。这种方法既避免了碎片的产生又不需要两块相同的内存空间因此其性价比较高。如图所示在通过根节点标记出所有可达对象后沿虚线进行对象移动将所有的可达对象都移动到一一端并保持它们之间的引用关系最后清理边界外的空间即可完成回收工作。标记压缩算法的最终效果等同于标记清除算法执行完成后再进行一次内存碎片整理因此也可以把它称为标记清除压缩(MarkSweepCompact)算法。代码实现/** * JVM标记-压缩算法Mark-Compact * 核心体现标记存活对象 → 计算新地址 → 压缩移动对象更新引用 → 清除垃圾解决内存碎片 */ public class MarkCompactAlgorithmDemo { // 1. 模拟JVM堆对象含ID、内存地址、存活标记、引用链、压缩后的新地址 static class JvmObject { private int id; // 对象唯一标识 private int memoryAddr; // 当前内存地址压缩前 private int newMemoryAddr; // 压缩后的新内存地址核心解决碎片 private boolean isMarked; // 存活标记 private ListJvmObject refs; // 当前对象引用的其他对象需同步更新引用地址 public JvmObject(int id, int memoryAddr) { this.id id; this.memoryAddr memoryAddr; this.newMemoryAddr -1; // 初始无新地址 this.isMarked false; this.refs new ArrayList(); } // 添加引用模拟对象间的引用关系 public void addReference(JvmObject obj) { if (obj ! null) { this.refs.add(obj); } } // 重置标记和新地址为下次GC做准备 public void reset() { this.isMarked false; this.newMemoryAddr -1; } // 打印对象信息含地址 Override public String toString() { return Object{id id , 原地址 memoryAddr , 新地址 (newMemoryAddr -1 ? 未分配 : newMemoryAddr) , 存活 isMarked }; } // 省略getter/setter public int getId() { return id; } public int getMemoryAddr() { return memoryAddr; } public void setMemoryAddr(int memoryAddr) { this.memoryAddr memoryAddr; } public int getNewMemoryAddr() { return newMemoryAddr; } public void setNewMemoryAddr(int newMemoryAddr) { this.newMemoryAddr newMemoryAddr; } public boolean isMarked() { return isMarked; } public void setMarked(boolean marked) { isMarked marked; } public ListJvmObject getRefs() { return refs; } } // 2. 标记-压缩GC管理器核心标记、计算新地址、压缩、清除 static class MarkCompactGC { // 模拟堆内存原内存地址, 对象TreeMap保证按地址排序 private final MapInteger, JvmObject heapMemory new TreeMap(); // GC Roots标记阶段的起点栈引用、静态变量等 private final SetJvmObject gcRoots new HashSet(); // 内存地址自增器模拟连续分配 private int nextMemoryAddr 100; // 初始地址从100开始每次100模拟固定大小对象 // 分配对象模拟JVM给新对象分配连续内存 public JvmObject allocateObject(int id) { int addr nextMemoryAddr; nextMemoryAddr 100; JvmObject obj new JvmObject(id, addr); heapMemory.put(addr, obj); System.out.println(分配对象 obj); return obj; } // 将对象加入GC Roots模拟栈引用/静态变量引用 public void addToGcRoots(JvmObject obj) { if (obj ! null) { gcRoots.add(obj); System.out.println(对象 obj.getId() 加入GC Roots); } } // 阶段1标记存活对象和标记-清除一致递归标记可达对象 private void mark() { System.out.println(\n 【标记阶段】开始标记存活对象 ); for (JvmObject rootObj : gcRoots) { markRecursive(rootObj); } // 打印标记结果 System.out.println(标记完成存活对象); heapMemory.values().stream() .filter(JvmObject::isMarked) .forEach(obj - System.out.println( obj)); } // 递归标记可达对象 private void markRecursive(JvmObject obj) { if (obj null || obj.isMarked()) { return; // 空对象或已标记直接返回 } obj.setMarked(true); System.out.println( 标记存活对象id obj.getId() 地址 obj.getMemoryAddr()); // 递归标记当前对象引用的所有对象 for (JvmObject refObj : obj.getRefs()) { markRecursive(refObj); } } // 阶段2计算存活对象的新地址核心紧凑排列消除碎片 private void calculateNewAddr() { System.out.println(\n 【压缩准备】计算存活对象的新地址 ); int newAddr 100; // 新地址从内存起始端100开始分配 // 按原地址排序遍历存活对象分配连续的新地址 for (JvmObject obj : heapMemory.values()) { if (obj.isMarked()) { obj.setNewMemoryAddr(newAddr); System.out.println( 对象 obj.getId() 原地址 obj.getMemoryAddr() → 新地址 newAddr); newAddr 100; // 保持对象内存块大小一致100 } } } // 阶段3压缩移动对象到新地址 更新所有引用 private void compact() { System.out.println(\n 【压缩阶段】移动对象更新引用 ); // 1. 移动存活对象到新地址重建堆内存映射 MapInteger, JvmObject newHeap new TreeMap(); for (JvmObject obj : heapMemory.values()) { if (obj.isMarked()) { int oldAddr obj.getMemoryAddr(); int newAddr obj.getNewMemoryAddr(); obj.setMemoryAddr(newAddr); // 更新对象自身的地址 newHeap.put(newAddr, obj); System.out.println( 移动对象id obj.getId() oldAddr → newAddr); } } // 2. 更新所有对象的引用关键避免引用指向旧地址 for (JvmObject obj : newHeap.values()) { ListJvmObject refs obj.getRefs(); for (int i 0; i refs.size(); i) { JvmObject refObj refs.get(i); if (refObj.isMarked()) { // 引用对象的地址更新为新地址 refs.set(i, newHeap.get(refObj.getNewMemoryAddr())); System.out.println( 更新对象 obj.getId() 的引用指向对象 refObj.getId() 的新地址 refObj.getNewMemoryAddr()); } } } // 替换为新的堆内存完成压缩 heapMemory.clear(); heapMemory.putAll(newHeap); } // 阶段4清除垃圾重置标记更新内存地址自增器 private void sweep() { System.out.println(\n 【清除阶段】释放垃圾内存 ); // 重置存活对象的标记和新地址为下次GC做准备 heapMemory.values().forEach(JvmObject::reset); // 更新下一次分配的内存地址指向压缩后的末尾100 if (!heapMemory.isEmpty()) { // 获取最大的新地址 int maxNewAddr heapMemory.keySet().stream().max(Integer::compare).orElse(100); nextMemoryAddr maxNewAddr 100; } else { nextMemoryAddr 100; // 堆为空重置地址 } System.out.println(清除完成下次分配地址从 nextMemoryAddr 开始); } // 执行GC标记 → 计算新地址 → 压缩 → 清除 public void doGC() { System.out.println(\n 触发标记-压缩GC ); System.out.println(GC前堆内存含碎片 heapMemory.values()); // 四步核心流程 mark(); // 标记存活对象 calculateNewAddr(); // 计算新地址 compact(); // 压缩移动更新引用 sweep(); // 清除垃圾重置 System.out.println(GC后堆内存无碎片 heapMemory.values()); printMemoryStatus(); // 打印内存状态验证无碎片 } // 打印内存状态验证是否有碎片 private void printMemoryStatus() { System.out.println(\n 内存状态分析 ); if (heapMemory.isEmpty()) { System.out.println( 堆内存为空无碎片); return; } // 提取存活对象的地址并排序 ListInteger addrs new ArrayList(heapMemory.keySet()); Collections.sort(addrs); System.out.println( 存活对象地址 addrs); // 检测碎片地址是否连续本demo中对象内存块100地址差应为100 boolean hasFragment false; for (int i 1; i addrs.size(); i) { if (addrs.get(i) - addrs.get(i-1) ! 100) { hasFragment true; break; } } if (hasFragment) { System.out.println( ❌ 存在内存碎片); } else { System.out.println( ✅ 内存地址连续无碎片); } } // 获取GC Roots测试用 public SetJvmObject getGcRoots() { return gcRoots; } } // 测试主方法模拟老年代场景验证压缩算法解决碎片问题 public static void main(String[] args) { MarkCompactGC gc new MarkCompactGC(); // 1. 分配对象建立引用关系模拟老年代高存活率场景 // 对象1地址100GC Roots栈引用 JvmObject obj1 gc.allocateObject(1); gc.addToGcRoots(obj1); // 对象2地址200被obj1引用 JvmObject obj2 gc.allocateObject(2); obj1.addReference(obj2); // 对象3地址300被obj2引用垃圾无GC Roots可达 JvmObject obj3 gc.allocateObject(3); obj2.addReference(obj3); // 但obj3无外部引用会被标记为垃圾 // 对象4地址400被obj1引用 JvmObject obj4 gc.allocateObject(4); obj1.addReference(obj4); // 对象5地址500垃圾无任何引用 JvmObject obj5 gc.allocateObject(5); // 2. 触发标记-压缩GC清理垃圾压缩存活对象消除碎片 gc.doGC(); // 3. 验证压缩后可分配大对象无碎片 System.out.println(\n 验证分配需要200连续内存的大对象 ); // 压缩后存活对象地址100、200、300连续下次分配地址400可分配200连续内存 JvmObject bigObj gc.allocateObject(6); System.out.println(✅ 大对象分配成功 bigObj); } }