当前位置: 首页 > news >正文

通过HTML演示JVM的垃圾回收-新生代与老年代

当内存溢出频发,或垃圾回收成为并发瓶颈时,理解 JVM 的内存布局和 GC 行为,是性能调优的关键。代码演示在目录的HTML演示中

目录

新生代(Young Generation)

Eden 区

Survivor 区

为什么需要 Survivor 区?

老年代(Old Generation)

GC 类型

HTML展示

新老年代转换演示

JVM垃圾回收算法演示


从垃圾回收的角度:

Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)

所以 Java 堆还可以细分为:新生代和老年代;再细致一点有:Eden、Survivor、Old 等空间

在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:

  1. 新生代内存(Young Generation)
  2. 老生代(Old Generation)
  3. 永久代(Permanent Generation)

下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间一层属于老年代,最下面一层属于永久代。

JDK 8 版本之后 PermGen(永久代/方法区) 已被 Metaspace(元空间) 取代,元空间使用的是本地内存。

新生代(Young Generation)

新生代分为三部分:

  • Eden 区:新对象几乎都首先分配在这里。
  • Survivor 区:包含两个等大小的区域 S0 和 S1,通常比例为 Eden : S0 : S1 = 8 : 1 : 1(可通过 -XX:SurvivorRatio 调整)。

Eden 区

  • 新对象优先分配在 Eden。
  • 大对象(如大数组)可能直接进入老年代,避免频繁复制。
  • Eden 空间不足时,触发 Minor GC(也称 Young GC),回收不可达对象。

Survivor 区

  • Minor GC 时,Eden 中存活的对象被复制到一个 Survivor 区(如 S0)。
  • 下一次 Minor GC 时,存活对象从 Eden + S0 复制到 S1,S0 被清空。
  • 两个 Survivor 区交替使用,始终有一个为空,这种机制称为 复制算法
  • 每次 Minor GC 后,存活对象的“年龄”加 1;达到阈值(默认 15,可通过 -XX:MaxTenuringThreshold 设置)后晋升到老年代。
为什么需要 Survivor 区?

若没有 Survivor 区,存活对象会直接进入老年代,导致:

  • 老年代快速填满,频繁触发 Full GC;
  • 无法区分短期与长期存活对象。

Survivor 区起到缓冲和筛选作用,只让真正长期存活的对象进入老年代,从而减少 Full GC 频率。


老年代(Old Generation)

老年代存放生命周期较长的对象,通常占堆内存的 2/3(默认新生代 : 老年代 = 1 : 2)。

以下对象会进入老年代:

  1. 长期存活对象:在新生代中经历多次 Minor GC 仍存活,且年龄达到阈值。
  2. 大对象:为避免复制开销,直接分配到老年代(部分 GC 支持)。
  3. Survivor 空间不足时的担保晋升:Minor GC 时若 Survivor 无法容纳所有存活对象,部分对象会提前晋升。
  4. 动态年龄判定:HotSpot 会判断,若某年龄以上对象总和超过 Survivor 一半,则直接晋升。

GC 类型

JVM 的垃圾回收按范围分为两类:

  • 部分收集(Partial GC)

    • Minor GC / Young GC:仅回收新生代。
    • Major GC / Old GC:仅回收老年代(注意:某些语境中 “Major GC” 也指 Full GC)。
    • Mixed GC:G1 GC 特有,回收新生代 + 部分老年代。
  • 整堆收集(Full GC):回收整个 Java 堆和方法区,通常代价高、STW 时间长,应尽量避免。

HTML展示

新老年代转换演示

可直接复制代码丢到HTML中查看

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>JVM 垃圾回收机制演示</title><style>body {font-family: "Microsoft YaHei", "Segoe UI", sans-serif;margin: 20px;background-color: #f5f7fa;color: #333;line-height: 1.6;}h1, h2 {text-align: center;color: #2c3e50;}.legend {text-align: center;margin: 15px 0;font-weight: bold;}.legend span {margin: 0 8px;padding: 4px 8px;border-radius: 4px;color: white;}.eden-legend { background-color: #e74c3c; }.s0-legend { background-color: #3498db; }.s1-legend { background-color: #2ecc71; }.old-legend { background-color: #9b59b6; }.dead-legend { background-color: #95a5a6; }.memory {display: flex;justify-content: center;gap: 20px;margin: 20px 0;flex-wrap: wrap;}.region {border: 2px solid #34495e;padding: 12px;width: 160px;text-align: center;background-color: white;border-radius: 10px;box-shadow: 0 2px 6px rgba(0,0,0,0.1);}.region h3 {margin-top: 0;font-size: 18px;color: #2c3e50;}.object {display: inline-block;margin: 4px;padding: 6px 10px;border-radius: 5px;color: white;font-weight: bold;font-size: 14px;}.eden .object { background-color: #e74c3c; }.s0 .object { background-color: #3498db; }.s1 .object { background-color: #2ecc71; }.old .object { background-color: #9b59b6; }.dead { opacity: 0.4; text-decoration: line-through; }#nextBtn {display: block;margin: 20px auto;padding: 12px 30px;font-size: 18px;background-color: #3498db;color: white;border: none;border-radius: 6px;cursor: pointer;}#nextBtn:hover { background-color: #2980b9; }#nextBtn:disabled { background-color: #bdc3c7; cursor: not-allowed; }.log {background-color: #ecf0f1;padding: 15px;border-radius: 8px;margin: 20px auto;max-width: 800px;font-family: monospace;font-size: 16px;text-align: center;color: #2c3e50;}.explanation {max-width: 900px;margin: 30px auto;padding: 20px;background-color: #fff;border-radius: 10px;box-shadow: 0 2px 8px rgba(0,0,0,0.1);}.explanation h3 {color: #2980b9;border-bottom: 1px solid #eee;padding-bottom: 8px;}ul {padding-left: 20px;}li {margin-bottom: 8px;}</style>
</head>
<body><h1>🎯 JVM 垃圾回收机制演示:新生代与老年代</h1><div class="legend"><span class="eden-legend">🔴 Eden</span><span class="s0-legend">🔵 S0</span><span class="s1-legend">🟢 S1</span><span class="old-legend">🟣 老年代</span><span class="dead-legend">💀 死亡对象</span>
</div><div class="memory"><div class="region eden"><h3>Eden</h3><div id="eden"></div></div><div class="region s0"><h3>Survivor S0</h3><div id="s0"></div></div><div class="region s1"><h3>Survivor S1</h3><div id="s1"></div></div><div class="region old"><h3>老年代</h3><div id="old"></div></div>
</div><div class="log" id="log">准备开始... 点击“下一步”查看 JVM 内存变化</div><button id="nextBtn">下一步</button><div class="explanation"><h3>📘 新生代(Young Generation)结构</h3><p>新生代通常被划分为三个部分:</p><ul><li><strong>Eden 区</strong>:新对象首先分配在此。</li><li><strong>Survivor 区</strong>:分为 S0 和 S1,比例通常为 Eden:S0:S1 = 8:1:1。</li></ul><h3>🔁 各区域作用</h3><ul><li><strong>Eden</strong>:对象创建地;满时触发 Minor GC。</li><li><strong>Survivor</strong>:Minor GC 时,存活对象在 S0/S1 之间“复制”;每经历一次 GC,年龄 +1。</li><li><strong>老年代</strong>:存放长期存活对象(年龄 ≥ 阈值,默认 15)或大对象。</li></ul><h3>❓ 为什么需要 Survivor 区?</h3><ul><li>避免短期存活对象直接进入老年代,防止老年代快速填满。</li><li>通过“多次筛选”,只让真正长期存活的对象晋升,减少 Full GC 频率。</li></ul><h3>⚙️ 晋升老年代的条件</h3><ul><li>对象年龄达到阈值(本演示设为 3)</li><li>Survivor 空间不足(分配担保)</li><li>动态年龄判定:某年龄以上对象总和 > Survivor 一半</li><li>大对象(如大数组)可能直接进入老年代</li></ul>
</div><script>// 初始化状态let eden = [];let s0 = [];let s1 = [];let old = [];let step = 0;const AGE_THRESHOLD = 3;function render() {const regions = { eden, s0, s1, old };for (const [name, list] of Object.entries(regions)) {const el = document.getElementById(name);if (el) {el.innerHTML = list.map(obj => `<div class="object ${!obj.alive ? 'dead' : ''}" title="年龄: ${obj.age}">${obj.name}</div>`).join('');}}}function log(msg) {const logEl = document.getElementById('log');if (logEl) logEl.innerText = msg;}function nextStep() {step++;try {switch (step) {case 1:eden = [{name:'A',age:0,alive:true}, {name:'B',age:0,alive:true}, {name:'C',age:0,alive:true}];log("✅ 创建 A、B、C → 放入 Eden(新对象首先进入 Eden)");break;case 2:if (eden.length >= 2) {eden[0].alive = false;eden[1].alive = false;}log("⏳ A、B 无引用(死亡),C 仍被引用(存活)");break;case 3:s0 = eden.filter(o => o.alive).map(o => ({...o, age: o.age + 1}));eden = [];log("♻️ Minor GC:回收 A、B;C(age=1) → S0;清空 Eden");break;case 4:eden = [{name:'D',age:0,alive:true}, {name:'E',age:0,alive:true}, {name:'F',age:0,alive:true}];log("✅ 创建 D、E、F → 放入 Eden");break;case 5:if (eden.length >= 1) eden[0].alive = false;log("⏳ D 死亡;E、F 存活;C(age=1) 在 S0 仍存活");break;case 6:const toS1 = [...s0, ...eden.filter(o => o.alive)].map(o => ({...o, age: o.age + 1}));s1 = toS1;s0 = [];eden = [];log("♻️ Minor GC:C(age=2)、E、F → S1;清空 Eden 和 S0");break;case 7:eden = [{name:'G',age:0,alive:true}, {name:'H',age:0,alive:true}];log("✅ 创建 G、H → 放入 Eden");break;case 8:if (eden.length >= 1) eden[0].alive = false;log("⏳ G 死亡;H 存活;C(age=2)、E、F 在 S1 仍存活");break;case 9:const candidates = [...s1, ...eden.filter(o => o.alive)].map(o => ({...o, age: o.age + 1}));const promote = candidates.filter(o => o.age >= AGE_THRESHOLD);const stay = candidates.filter(o => o.age < AGE_THRESHOLD);old.push(...promote);s0 = stay;s1 = [];eden = [];const promotedNames = promote.map(o => o.name).join(', ');const msg = promote.length > 0 ? `♻️ Minor GC:H、E、F → S0;${promotedNames}(age=${AGE_THRESHOLD}) 晋升老年代!` : "♻️ Minor GC:所有存活对象 → S0";log(msg);break;case 10:log("🎉 演示结束!C 已进入老年代");document.getElementById('nextBtn').disabled = true;return;default:if (step > 10) return;}render();} catch (e) {console.error("演示步骤出错:", e);log("⚠️ 演示异常,请刷新页面重试。");}}// 绑定按钮事件(避免内联 onclick)document.addEventListener('DOMContentLoaded', () => {const btn = document.getElementById('nextBtn');if (btn) {btn.addEventListener('click', nextStep);}render();});
</script></body>
</html>

JVM垃圾回收算法演示

当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 Java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

比如在新生代中,每次收集都会有大量对象死去,所以可以选择“复制”算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

代码

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>JVM垃圾回收算法详细动画演示</title><style>body {font-family: "Microsoft YaHei", sans-serif;background-color: #eef2f3;margin: 0;padding: 20px;color: #333;}h1 {text-align: center;color: #2c3e50;}.container {display: flex;flex-wrap: wrap;justify-content: space-around;gap: 20px;}.algorithm-section {background-color: #ffffff;border-radius: 10px;box-shadow: 0 4px 8px rgba(0,0,0,0.1);padding: 20px;width: 45%;min-width: 550px;box-sizing: border-box;}.algorithm-title {text-align: center;margin-top: 0;color: #2c3e50;}.animation-area {width: 100%;height: 200px;border: 1px solid #ccc;position: relative;overflow: hidden;margin-bottom: 15px;background-color: #fafafa;}.memory-block {position: absolute;height: 30px;top: 80px;display: flex;align-items: center;justify-content: center;font-weight: bold;color: white;transition: all 0.5s ease;}.object-alive { background-color: #27ae60; } /* Green */.object-dead { background-color: #95a5a6; } /* Gray */.object-marked { background-color: #3498db; } /* Blue */.object-moved { background-color: #8e44ad; } /* Purple */.object-copied { background-color: #f39c12; } /* Orange */.memory-region {position: absolute;height: 60px;top: 70px;display: flex;align-items: center;justify-content: center;font-weight: bold;color: #2c3e50;border: 1px dashed;}.region-from { border-color: #3498db; background-color: #ebf5fb; }.region-to { border-color: #27ae60; background-color: #e8f8f5; }.region-young { border-color: #3498db; background-color: #ebf5fb; }.region-old { border-color: #8e44ad; background-color: #f4ecf7; }.pointer {position: absolute;top: 40px;height: 10px;width: 2px;background-color: #e74c3c;transition: left 0.5s;}.phase-info {text-align: center;height: 40px;font-weight: bold;color: #2c3e50;}button {display: block;margin: 10px auto;padding: 10px 20px;background-color: #3498db;color: white;border: none;border-radius: 5px;cursor: pointer;font-size: 16px;}button:hover { background-color: #2980b9; }button:disabled { background-color: #bdc3c7; cursor: not-allowed; }.description {font-size: 16px;line-height: 1.5;}@media (max-width: 1200px) {.algorithm-section { width: 90%; }}</style>
</head>
<body><h1>JVM垃圾回收算法动画演示</h1><div class="container"><!-- 标记-清除算法 --><div class="algorithm-section"><h2 class="algorithm-title">标记-清除算法 (Mark-and-Sweep)</h2><div class="phase-info" id="mark-sweep-phase">准备开始...</div><div class="animation-area" id="mark-sweep-animation"></div><button onclick="startMarkSweepDetailed()">开始演示</button><div class="description"><p><strong>特点:</strong> 分为标记和清除两个阶段。标记所有存活对象,然后清除未标记的对象。</p><p><strong>局限性:</strong> 效率问题(标记和清除过程较慢);空间问题(产生内存碎片)。</p></div></div><!-- 复制算法 --><div class="algorithm-section"><h2 class="algorithm-title">复制算法 (Copying)</h2><div class="phase-info" id="copying-phase">准备开始...</div><div class="animation-area" id="copying-animation"></div><button onclick="startCopyingDetailed()">开始演示</button><div class="description"><p><strong>特点:</strong> 将内存分为两块,每次只使用一块。GC时将存活对象复制到另一块,清理已用空间。</p><p><strong>局限性:</strong> 可用内存减半;不适合老年代(存活对象多时复制开销大)。</p></div></div><!-- 标记-整理算法 --><div class="algorithm-section"><h2 class="algorithm-title">标记-整理算法 (Mark-and-Compact)</h2><div class="phase-info" id="compact-phase">准备开始...</div><div class="animation-area" id="compact-animation"></div><button onclick="startCompactingDetailed()">开始演示</button><div class="description"><p><strong>特点:</strong> 在标记后,将所有存活对象向一端移动,然后清理边界外的内存。</p><p><strong>局限性:</strong> 效率较低(多了整理步骤),适用于老年代这种GC频率低的场景。</p></div></div><!-- 分代收集算法 --><div class="algorithm-section"><h2 class="algorithm-title">分代收集算法 (Generational Collection)</h2><div class="phase-info" id="generational-phase">准备开始...</div><div class="animation-area" id="generational-animation"></div><button onclick="startGenerationalDetailed()">开始演示</button><div class="description"><p><strong>特点:</strong> 根据对象存活周期将堆分为新生代和老年代,分别采用合适的算法。</p><p><strong>原因:</strong> 新生代对象“朝生夕死”,适合复制算法;老年代对象存活率高,适合标记-清除/整理。</p></div></div></div><script>// --- Common Utilities ---function createElement(tag, className, styles = {}) {const el = document.createElement(tag);if (className) el.className = className;Object.assign(el.style, styles);return el;}function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }// --- Mark-and-Sweep Algorithm (Detailed) ---async function startMarkSweepDetailed() {const area = document.getElementById('mark-sweep-animation');const info = document.getElementById('mark-sweep-phase');const button = event.target;button.disabled = true;area.innerHTML = '';const objects = [{id: 'A', marked: false, alive: true},{id: 'B', marked: false, alive: false},{id: 'C', marked: false, alive: true},{id: 'D', marked: false, alive: false},{id: 'E', marked: false, alive: true},{id: 'F', marked: false, alive: false}];const objectElements = [];const totalWidth = area.clientWidth - 20;const objectWidth = totalWidth / objects.length - 10;objects.forEach((obj, i) => {const el = createElement('div', `memory-block object-${obj.alive ? 'alive' : 'dead'}`, {width: `${objectWidth}px`, left: `${i * (objectWidth + 10) + 10}px`});el.innerText = obj.id;area.appendChild(el);objectElements.push({data: obj, element: el});});info.innerText = "步骤 1: 对象创建完成,B, D, F 无引用变为垃圾。";await sleep(1500);info.innerText = "步骤 2: GC启动,进入标记阶段。";await sleep(1000);info.innerText = "步骤 3: 从GC Roots开始遍历,标记存活对象...";await sleep(1000);for (let item of objectElements) {if (item.data.alive) {item.data.marked = true;item.element.className = 'memory-block object-marked';await sleep(600);}}info.innerText = "步骤 4: 标记阶段完成。";await sleep(1000);info.innerText = "步骤 5: 进入清除阶段,回收未标记对象...";await sleep(1000);for (let item of objectElements) {if (!item.data.marked) {item.element.style.opacity = '0.3';item.element.style.transform = 'scale(0.8)';await sleep(600);}}info.innerText = "步骤 6: 清除完成,产生内存碎片。标记-清除算法演示结束。";button.disabled = false;}// --- Copying Algorithm (Detailed) ---async function startCopyingDetailed() {const area = document.getElementById('copying-animation');const info = document.getElementById('copying-phase');const button = event.target;button.disabled = true;area.innerHTML = '';const fromSpace = createElement('div', 'memory-region region-from', {left: '10px', width: 'calc(50% - 20px)'});fromSpace.innerText = 'From Space';const toSpace = createElement('div', 'memory-region region-to', {right: '10px', width: 'calc(50% - 20px)'});toSpace.innerText = 'To Space';area.appendChild(fromSpace);area.appendChild(toSpace);const objects = [{id: 'X', alive: true}, {id: 'Y', alive: false},{id: 'Z', alive: true}, {id: 'W', alive: true}];const objectElements = [];const spaceWidth = (area.clientWidth / 2) - 30;const objectWidth = spaceWidth / objects.length - 10;objects.forEach((obj, i) => {const el = createElement('div', `memory-block object-${obj.alive ? 'alive' : 'dead'}`, {width: `${objectWidth}px`, left: `${i * (objectWidth + 10) + 20}px`, top: '85px'});el.innerText = obj.id;fromSpace.appendChild(el);objectElements.push({data: obj, element: el});});info.innerText = "步骤 1: Eden区 (From Space) 对象分配完成,Y死亡。";await sleep(1500);info.innerText = "步骤 2: Minor GC 触发,准备进行复制回收。";await sleep(1000);info.innerText = "步骤 3: 遍历From Space,识别存活对象...";await sleep(1000);info.innerText = "步骤 4: 将存活对象(X, Z, W)依次复制到To Space...";await sleep(1000);let toIndex = 0;for (let item of objectElements) {if (item.data.alive) {const newEl = item.element.cloneNode(true);newEl.className = 'memory-block object-copied';newEl.style.left = `${toIndex * (objectWidth + 10) + 20}px`;newEl.style.top = '85px';toSpace.appendChild(newEl);toIndex++;await sleep(800);}}info.innerText = "步骤 5: 复制完成,清空整个From Space...";await sleep(1000);fromSpace.innerHTML = 'From Space<br/>(已清空)';fromSpace.style.backgroundColor = '#fdebd0';info.innerText = "步骤 6: From和To角色互换。复制算法演示结束。";button.disabled = false;}// --- Mark-and-Compact Algorithm (Detailed) ---async function startCompactingDetailed() {const area = document.getElementById('compact-animation');const info = document.getElementById('compact-phase');const button = event.target;button.disabled = true;area.innerHTML = '';const objects = [{id: 'M', marked: false, alive: true},{id: 'N', marked: false, alive: false},{id: 'O', marked: false, alive: true},{id: 'P', marked: false, alive: false},{id: 'Q', marked: false, alive: true},{id: 'R', marked: false, alive: false}];const objectElements = [];const totalWidth = area.clientWidth - 20;const objectWidth = totalWidth / objects.length - 10;objects.forEach((obj, i) => {const el = createElement('div', `memory-block object-${obj.alive ? 'alive' : 'dead'}`, {width: `${objectWidth}px`, left: `${i * (objectWidth + 10) + 10}px`});el.innerText = obj.id;area.appendChild(el);objectElements.push({data: obj, element: el});});info.innerText = "步骤 1: 老年代内存状态,N, P, R 为垃圾对象。";await sleep(1500);info.innerText = "步骤 2: Full GC 启动,进入标记阶段。";await sleep(1000);info.innerText = "步骤 3: 标记所有存活对象...";await sleep(1000);for (let item of objectElements) {if (item.data.alive) {item.data.marked = true;item.element.className = 'memory-block object-marked';await sleep(600);}}info.innerText = "步骤 4: 标记完成。";await sleep(1000);info.innerText = "步骤 5: 进入整理阶段,将存活对象向内存一端移动...";await sleep(1000);let compactIndex = 0;for (let item of objectElements) {if (item.data.marked) {item.element.className = 'memory-block object-moved';const targetLeft = compactIndex * (objectWidth + 10) + 10;const currentLeft = parseInt(item.element.style.left);const delta = targetLeft - currentLeft;item.element.style.transform = `translateX(${delta}px)`;compactIndex++;await sleep(700);}}info.innerText = "步骤 6: 整理完成,清理边界外的内存...";await sleep(1000);for (let item of objectElements) {if (!item.data.marked) {item.element.style.opacity = '0.3';item.element.style.transform += ' scale(0.8)';await sleep(600);}}info.innerText = "步骤 7: 内存整理完毕,无碎片。演示结束。";button.disabled = false;}// --- Generational Collection Algorithm (Detailed) ---async function startGenerationalDetailed() {const area = document.getElementById('generational-animation');const info = document.getElementById('generational-phase');const button = event.target;button.disabled = true;area.innerHTML = '';const youngGen = createElement('div', 'memory-region region-young', {top: '10px', left: '10px', width: 'calc(100% - 20px)'});youngGen.innerText = '新生代 (Young Generation)';const oldGen = createElement('div', 'memory-region region-old', {bottom: '10px', left: '10px', width: 'calc(100% - 20px)'});oldGen.innerText = '老年代 (Old Generation)';area.appendChild(youngGen);area.appendChild(oldGen);info.innerText = "步骤 1: JVM内存分为新生代和老年代。";await sleep(1500);info.innerText = "步骤 2: 新生代对象'朝生夕死',采用复制算法。";await sleep(1000);youngGen.style.boxShadow = 'inset 0 0 10px #3498db';await sleep(1000);youngGen.style.boxShadow = 'none';await sleep(500);info.innerText = "步骤 3: 老年代对象存活率高,采用标记-清除/整理。";await sleep(1000);oldGen.style.boxShadow = 'inset 0 0 10px #8e44ad';await sleep(1000);oldGen.style.boxShadow = 'none';await sleep(500);info.innerText = "步骤 4: 对象在新生代经历多次GC后晋升到老年代。";await sleep(1000);const obj = createElement('div', 'memory-block object-alive', {width: '30px', height: '20px', top: '25px', left: '45%', fontSize: '12px'});obj.innerText = 'Obj';youngGen.appendChild(obj);await sleep(1000);obj.style.transition = 'all 1s ease';obj.style.top = '155px';obj.style.backgroundColor = '#8e44ad';await sleep(1200);info.innerText = "步骤 5: 不同代使用最适合的算法,这就是分代收集。";await sleep(1500);info.innerText = "分代收集算法演示完成。";button.disabled = false;}</script></body>
</html>

http://www.dtcms.com/a/438948.html

相关文章:

  • 网页制作模板的网站网站开发工程师的职务
  • C语言自学--自定义类型:联合和枚举
  • 1.2.2 Function Calling:让 LLM 具备“超能力
  • 通过邮箱查注册网站织梦汽车网站模板免费下载
  • 【附源码】基于Spring Boot的4S店信息管理系统 的设计与实现
  • 工程公司注册经营范围南阳网站优化哪家好
  • LINUX——进度条
  • 淘宝客新增网站可以做黄金期权的网站
  • 微信公众号移动网站开发大连建设银行官网招聘网站
  • 【C++】map与set底层结构——红黑树
  • 知乎 wordpress主题商丘市网络优化公司地址
  • 企业网站设计制作收费6黄页网站建设
  • 注册网站商标长垣网站建设
  • 栈的压入弹出序列--牛客
  • 深圳设计网站南宁专业做网站
  • 同ip网站有什么危害不动产网站建设
  • 卫星通信天线极化角偏差对天线增益、交叉极化隔离度的影响
  • 好用的ppt模板网站公司网站建设费会计分录
  • Day92 基本情报技术者 单词表28 AI応用
  • 蛋糕店网站开发策划书公司网站优点
  • 网络卖东西的平台有哪些公司网站优化推广
  • 2025 AI 治理困局:假新闻围剿与隐私保护的双重博弈
  • 一个RCE命令执行靶场,包含基础命令、shell 特性、常见姿势、常见waf绕过
  • 受欢迎的福州网站建设wordpress后台模块
  • 李宏毅-Generative AI-第一课
  • 服务器安装完面板怎么做网站免费h5页面制作app
  • datawhale RAG技术全栈指南 202509 第5次作业
  • 怎么屏蔽ip段访问网站哪个做网站好
  • 手机电商网站 模板工程信息建程网
  • 找人做网站 自己购买服务器图片链接生成器软件