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

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

目录

[新生代(Young Generation)](#新生代(Young Generation))

[Eden 区](#Eden 区)

[Survivor 区](#Survivor 区)

[为什么需要 Survivor 区?](#为什么需要 Survivor 区?)

[老年代(Old Generation)](#老年代(Old Generation))

[GC 类型](#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 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

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

代码

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", 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>
相关推荐
palpitation972 小时前
Android App Links 配置
前端
FuckPatience2 小时前
Vue 组件定义模板,集合v-for生成界面
前端·javascript·vue.js
sophie旭2 小时前
一道面试题,开始性能优化之旅(3)-- DNS查询+TCP(三)
前端·面试·性能优化
开心不就得了3 小时前
构建工具webpack
前端·webpack·rust
gerrgwg3 小时前
Flutter中实现Hero Page Route效果
前端
不枯石3 小时前
Matlab通过GUI实现点云的ICP配准
linux·前端·图像处理·计算机视觉·matlab
hhzz3 小时前
Pythoner 的Flask项目实践-在web页面实现矢量数据转换工具集功能(附源码)
前端·python·flask
lypzcgf3 小时前
Coze源码分析-资源库-编辑工作流-前端源码-核心流程/API/总结
前端·工作流·coze·coze源码分析·智能体平台·ai应用平台·agent平台
lypzcgf3 小时前
Coze源码分析-资源库-编辑工作流-前端源码-核心组件
前端·工作流·coze·coze源码分析·智能体平台·agent平台