深入JVM底层-垃圾回收GC算法

1、标记-清除算法(Mark-Sweep):

原理: 标记所有存活对象,然后清除未标记的垃圾对象。

标记阶段: 遍历所有GC Roots(如栈帧中的局部变量、静态变量等),标记所有可达(存活)对象。

清除阶段: 扫描整个堆内存,回收未被标记的对象(即垃圾对象)所占用的空间。

优点:

实现简单,适用于大多数对象生命周期较长的场景(如老年代)。不需要额外的内存空间(如复制算法需要双倍内存)。

缺点:

内存碎片化,回收后会产生不连续的内存空间,可能导致大对象无法分配。STW(Stop-The-World):标记和清除阶段通常需要暂停所有应用线程,影响性能。

应用: 老年代(如CMS垃圾回收器的部分阶段)。

算法图解:

代码示例:

python 复制代码
class Object:
    def __init__(self, name):
        self.name = name
        # 标记位,初始为未标记
        self.marked = False

class MarkSweepGC:
    def __init__(self):
        # 所有对象
        self.objects = []
        # 根对象(如全局变量、活动栈中的引用)
        self.roots = []

    def add_object(self, obj):
        self.objects.append(obj)

    def add_root(self, obj):
        self.roots.append(obj)

    def mark(self):
        # 从根对象开始标记
        stack = self.roots.copy()
        while stack:
            obj = stack.pop()
            if not obj.marked:
                obj.marked = True
                print(f"标记对象: {obj.name}")

    def sweep(self):
        # 遍历所有对象,清除未标记的对象
        # 使用副本遍历以便安全删除
        for obj in self.objects[:]:
            if not obj.marked:
                print(f"清除对象: {obj.name}")
                self.objects.remove(obj)
            else:
                # 清除标记位,为下一次GC做准备
                obj.marked = False

    def gc(self):
        print("开始垃圾回收...")
        self.mark()
        self.sweep()
        print("垃圾回收完成")

if __name__ == "__main__":
    gc = MarkSweepGC()
    # 创建一些对象
    obj1 = Object("对象1")
    obj2 = Object("对象2")
    obj3 = Object("对象3")
    obj4 = Object("对象4")

    # 添加对象到GC管理
    gc.add_object(obj1)
    gc.add_object(obj2)
    gc.add_object(obj3)
    gc.add_object(obj4)

    # 设置根对象
    gc.add_root(obj1)
    gc.add_root(obj2)
    # 手动触发垃圾回收
    gc.gc()
    # 检查剩余对象
    print("\n剩余对象:")
    for obj in gc.objects:
        print(obj.name)

2、 标记-整理算法(Mark-Compact):

原理: 在标记后对存活对象进行整理压缩

标记阶段: 从根对象(如全局变量、栈上的变量等)出发,遍历所有可达对象,并对其进行标记。

整理阶段: 将所有存活的对象向内存一端移动,然后清理掉边界以外的内存。

优点: 解决了内存碎片问题,分配新对象时只需简单的指针递增(类似栈分配),不需要像复制算法那样浪费一半内存空间

缺点: 需要多次遍历堆内存,对象移动导致程序暂停时间较长(Stop-The-World),需要更新所有引用关系,增加了复杂性

应用: .NET的托管堆垃圾回收;Java的某些GC实现(如Serial GC、Parallel GC在老年代的回收);内存受限且对碎片敏感的环境

算法图解:

代码示例:

python 复制代码
class Object:
    def __init__(self, id, size, references=None):
        # 对象唯一标识
        self.id = id
        # 对象大小
        self.size = size
        # 引用的其他对象
        self.references = references if references else []
        # 标记位
        self.marked = False
        # 整理后新地址
        self.forwarded = None

    def __repr__(self):
        return f"Object({self.id}, size={self.size})"

class MarkCompactGC:
    def __init__(self):
        # 所有对象
        self.objects = []
        # 模拟堆内存
        self.heap = []

    def allocate(self, id, size, references=None):
        """分配对象"""
        obj = Object(id, size, references)
        self.objects.append(obj)
        self.heap.extend([obj] * size)
        return obj

    def mark(self, roots):
        """标记阶段:从根对象开始标记所有可达对象"""
        stack = list(roots)
        while stack:
            obj = stack.pop()
            if not obj.marked:
                obj.marked = True
                # 将所有引用对象加入栈
                for ref in obj.references:
                    if not ref.marked:
                        stack.append(ref)

    def compute_addresses(self):
        """计算整理后的新地址"""
        # 收集所有存活对象(已标记)
        live_objects = [obj for obj in self.objects if obj.marked]
        # 按地址顺序排序
        live_objects.sort(key=lambda x: id(x))
        # 计算新地址
        new_address = 0
        for obj in live_objects:
            obj.forwarded = new_address
            new_address += obj.size

    def relocate(self):
        """整理阶段:移动对象到新位置"""
        # 创建一个新堆
        new_heap = []
        # 按新地址顺序重新排列对象
        live_objects = sorted(
            [obj for obj in self.objects if obj.marked],
            key=lambda x: x.forwarded)
        for obj in live_objects:
            # 填充对象到新位置
            new_heap.extend([obj] * obj.size)
            # 更新对象的引用(指向新地址)
            for i in range(len(obj.references)):
                ref = obj.references[i]
                # 只更新指向存活对象的引用
                if ref.marked:
                    obj.references[i] = next(o for o in live_objects if o.id == ref.id)

        # 更新堆和对象列表
        self.heap = new_heap
        self.objects = live_objects
        # 重置标记
        for obj in self.objects:
            obj.marked = False
            obj.forwarded = None

    def compact(self, roots):
        """执行完整的标记-整理过程"""
        # 1. 标记阶段
        self.mark(roots)
        # 2. 计算新地址
        self.compute_addresses()
        # 3. 整理阶段
        self.relocate()

    def print_heap(self):
        """打印堆状态"""
        print("Heap contents:")
        for i, obj in enumerate(self.heap):
            if i == 0 or obj != self.heap[i - 1]:
                print(f"{i}: {obj}")
        print()

if __name__ == "__main__":
    gc = MarkCompactGC()
    # 创建一些对象
    obj1 = gc.allocate("A", 2)
    obj2 = gc.allocate("B", 1)
    obj3 = gc.allocate("C", 3)
    obj4 = gc.allocate("D", 1)
    obj5 = gc.allocate("E", 2)

    # 设置引用关系
    obj1.references = [obj2, obj3]
    obj2.references = [obj4]
    obj3.references = [obj4, obj5]
    print("初始堆:")
    gc.print_heap()
    # 只有obj1是根对象
    roots = [obj1]
    print("执行垃圾回收...")
    gc.compact(roots)
    print("整理后的堆:")
    gc.print_heap()

3、 复制算法(Copying):

原理: 将可用内存划分为两个大小相等的区域:From空间和To空间;新对象分配在From空间中;当From空间满时,触发垃圾回收;最后交换From和To空间的角色(即原来的To空间变为新的From空间)

优点: 高效:只需要遍历存活对象,不处理死亡对象;无碎片:每次复制后对象都紧凑排列,不会产生内存碎片;简单快速:实现简单,回收速度快

缺点: 内存利用率低:任何时候都有一半内存空间闲置;对象移动开销:存活对象较多时,复制开销大;不适合老年代:通常只用于新生代回收

应用: 新生代通常分为Eden区和两个Survivor区(From和To)

大多数实现使用"Appel式回收",即Eden+From→To的复制

经过多次回收仍存活的对象会被晋升到老年代

算法图解:

代码示例:

python 复制代码
import random

class Object:
    def __init__(self, id, size):
        self.id = id
        self.size = size
        # 模拟对象是否存活
        self.marked = False

    def __repr__(self):
        return f"Obj-{self.id}({self.size}KB)"

class JVMMemory:
    def __init__(self, eden_size=1024, survivor_size=512):
        # 初始化内存区域 (单位KB)
        self.eden = []
        self.from_survivor = []
        self.to_survivor = []
        # 各区域最大容量
        self.eden_size = eden_size
        self.survivor_size = survivor_size
        # 对象ID计数器
        self.object_id = 0

    def allocate(self, size):
        """在Eden区分配对象"""
        if self.used_memory(self.eden) + size > self.eden_size:
            print("Eden区空间不足,触发Young GC!")
            self.young_gc()
            # GC后再次尝试分配
            if self.used_memory(self.eden) + size > self.eden_size:
                raise MemoryError("Eden区仍空间不足")
        obj = Object(self.object_id, size)
        self.object_id += 1
        self.eden.append(obj)
        print(f"分配 {obj} 到Eden区")
        return obj

    def used_memory(self, region):
        """计算区域已用内存"""
        return sum(obj.size for obj in region)

    def young_gc(self):
        """模拟Young GC的复制算法"""
        print("\n=== 开始Young GC ===")
        print(f"GC前: Eden={len(self.eden)}对象, From={len(self.from_survivor)}对象")
        # 1. 标记存活对象
        for obj in self.eden:
            # 20%存活率
            obj.marked = random.random() > 0.8
        for obj in self.from_survivor:
            # 20%存活率
            obj.marked = random.random() > 0.8
        # 2. 复制存活对象到To Survivor区
        self.to_survivor.clear()
        total_copied = 0
        # 从Eden复制
        for obj in self.eden:
            if obj.marked:
                if self.used_memory(self.to_survivor) + obj.size > self.survivor_size:
                    print(f"Survivor区空间不足,对象{obj}直接晋升到老年代")
                    continue
                self.to_survivor.append(obj)
                total_copied += obj.size
                print(f"复制 {obj} 到To Survivor区")

        # 从From Survivor复制
        for obj in self.from_survivor:
            if obj.marked:
                if self.used_memory(self.to_survivor) + obj.size > self.survivor_size:
                    print(f"Survivor区空间不足,对象{obj}直接晋升到老年代")
                    continue
                self.to_survivor.append(obj)
                total_copied += obj.size
                print(f"复制 {obj} 到To Survivor区")

        # 3. 交换Survivor区
        self.from_survivor, self.to_survivor = self.to_survivor, self.from_survivor
        # 4. 清空Eden和旧的From Survivor
        self.eden.clear()
        self.to_survivor.clear()
        print(f"GC完成: 复制了{len(self.from_survivor)}个存活对象, 共{total_copied}KB")
        print(f"GC后: Eden={len(self.eden)}对象, From={len(self.from_survivor)}对象\n")

    def print_memory(self):
        """打印内存使用情况"""
        print("\n内存状态:")
        print(f"Eden区: {len(self.eden)}对象, 已用{self.used_memory(self.eden)}KB")
        print(f"From Survivor区: {len(self.from_survivor)}对象, 已用{self.used_memory(self.from_survivor)}KB")
        print(f"To Survivor区: {len(self.to_survivor)}对象, 已用{self.used_memory(self.to_survivor)}KB")

def jvm_copying():
    memory = JVMMemory()
    # 分配一些对象
    for _ in range(5):
        # 对象大小
        size = random.randint(5, 60)
        memory.allocate(size)
    memory.print_memory()
    # 手动触发GC
    memory.young_gc()
    # 分配更多对象
    for _ in range(4):
        size = random.randint(5, 60)
        memory.allocate(size)
    memory.print_memory()

if __name__ == "__main__":
    jvm_copying()

4、 分代收集算法(Generational Collection):

原理: 将堆内存划分为不同的代(Generation),对不同代采用不同的垃圾收集策略。

年轻代 (Young Generation) 使用复制算法(高效,因为存活对象少)

Eden区:新对象首先分配在这里

Survivor区(S0和S1):用于存放从Eden区经过Minor GC后存活的对象

老年代 (Old Generation)

使用标记-清除或标记-整理算法(减少内存碎片),存放长期存活的对象

优点: 针对不同对象生命周期优化,频繁收集年轻代(Minor GC),较少收集老年代(Major GC),通过这种分而治之的策略,有效提高了垃圾收集的效率,减少了停顿时间。

缺点: 实现复杂。

应用: Java HotSpot VM中的分代收集器组合:

年轻代:Parallel Scavenge(并行收集)

老年代:Parallel Old(并行标记整理)

算法图解:

代码示例:

python 复制代码
import random
import time

class Object:
    def __init__(self, id):
        self.id = id
        # 对象年龄
        self.age = 0

class GenerationalGC:
    def __init__(self):
        # 初始化两代:年轻代和老年代
        self.young_gen = []
        self.old_gen = []
        self.total_objects = 0
        self.survived_objects = 0

    def allocate(self):
        obj = Object(self.total_objects)
        self.young_gen.append(obj)
        self.total_objects += 1
        return obj

    def minor_gc(self):
        """年轻代GC(Minor GC)"""
        print("\n[Minor GC] 开始年轻代回收...")
        initial_count = len(self.young_gen)
        # 30%存活概率
        survivors = [obj for obj in self.young_gen if random.random() <= 0.3]
        for obj in survivors:
            obj.age += 1

        # 晋升逻辑:年龄>1的存活对象
        promoted = [obj for obj in survivors if obj.age > 1]
        kept_in_young = [obj for obj in survivors if obj.age <= 1]
        # 执行晋升
        self.old_gen.extend(promoted)
        self.young_gen = kept_in_young
        # 修正统计计算
        collected = initial_count - len(survivors)
        self.survived_objects += len(survivors)
        print(f"回收了 {collected} 个对象")
        print(f"晋升了 {len(promoted)} 个对象到老年代")
        print(f"年轻代剩余: {len(self.young_gen)} | 老年代: {len(self.old_gen)}")

    def major_gc(self):
        """老年代GC(Major GC)"""
        print("\n[Major GC] 开始老年代回收...")
        initial_old = len(self.old_gen)
        # 50%存活概率
        self.old_gen = [obj for obj in self.old_gen if random.random() <= 0.5]
        # 同时触发年轻代回收
        self.minor_gc()
        collected_old = initial_old - len(self.old_gen)
        print(f"老年代剩余: {len(self.old_gen)}")

    def simulate(self, steps=10, gc_threshold=5):
        """模拟对象分配和GC过程"""
        for step in range(steps):
            print(f"\n=== 步骤 {step + 1} ===")
            # 随机分配对象
            num_alloc = random.randint(2, 5)
            for _ in range(num_alloc):
                self.allocate()
            print(f"分配了 {num_alloc} 个新对象 (总计: {len(self.young_gen)}年轻 + {len(self.old_gen)}老)")
            # 根据阈值触发GC
            if len(self.young_gen) >= 10 or (step % 3 == 0 and len(self.young_gen) > 3):
                self.minor_gc()
            # 定期执行老年代GC
            if step % 4 == 3:
                self.major_gc()
            time.sleep(0.2)

if __name__ == "__main__":
    print("分代垃圾回收模拟开始 (年轻代+老年代)")
    gc = GenerationalGC()
    gc.simulate(steps=12)
    print("\n=== 最终统计 ===")
    print(f"总创建对象: {gc.total_objects}")
    print(f"存活对象: {gc.survived_objects}")
    print(f"老年代对象: {len(gc.old_gen)}")

5、 增量收集算法(Incremental GC)

原理:

分而治之:将传统的"全停顿"(Stop-The-World)垃圾回收过程分解为多个小步骤

交替执行:让垃圾回收与用户程序交替运行,每次只执行一小部分垃圾回收工作

减少延迟:通过这种方式减少单次垃圾回收造成的程序停顿时间,提高系统响应性

优点: 减少单次GC停顿时间,提高系统响应速度,适合交互式应用

缺点: 总体GC时三色标记法(Tri-color Marking),写屏障(Write Barrier)技术

应用: HotSpot JVM的G1收集器的重要组成部分

算法图解:

代码示例:

python 复制代码
import gc
import tracemalloc

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

def create_circular_ref():
    """创建循环引用,但不返回任何引用"""
    head = Node(1)
    current = head
    for i in range(2, 6):
        current.next = Node(i)
        current = current.next
        # 形成循环引用
    current.next = head

def analyze_snapshot(snapshot):
    """仅分析用户代码的内存分配"""
    top_stats = snapshot.statistics('traceback')
    for stat in top_stats:
        for frame in stat.traceback:
            if "incremental_gc.py" in frame.filename:
                print(stat)
                break

def incremental_gc_demo():
    # 禁用自动GC
    gc.disable()
    tracemalloc.start()
    print("创建循环引用对象...")
    create_circular_ref()
    snapshot1 = tracemalloc.take_snapshot()
    print("\n[内存分配统计(创建后)]")
    analyze_snapshot(snapshot1)
    print("\n开始增量垃圾回收...")
    print(f"[阶段1] 回收第0代: {gc.collect(0)}")
    print(f"[阶段2] 回收第1代: {gc.collect(1)}")
    print(f"[阶段3] 回收所有代: {gc.collect(2)}")
    # 清理无法回收的对象
    gc.garbage.clear()
    snapshot2 = tracemalloc.take_snapshot()
    print("\n[内存分配统计(回收后)]")
    analyze_snapshot(snapshot2)
    print("\n最终垃圾回收统计:")
    print(f"无法回收的对象: {gc.garbage}")
    print(f"各代对象计数: {gc.get_count()}")

if __name__ == "__main__":
    # Python解释器 或 tracemalloc自身的内存 会存在计数差异
    incremental_gc_demo()

6、 并发收集算法(Concurrent GC)

原理: 间可能更长,需要额外的写屏障开销,实现复杂度较高

分类:

并发执行:垃圾收集器与应用程序线程同时运行

减少停顿:最小化或消除长时间的全局停顿

增量处理:将垃圾收集工作分成多个小步骤执行

安全点协调:在必要时短暂暂停应用线程进行同步

算法示例 三色标记算法、并发标记-清除(CMS)、G1收集器(Garbage-First)、ZGC和Shenandoah

应用: 对延迟敏感的应用(如交易系统)、大堆内存应用(减少Full GC影响)、实时系统(要求可预测的停顿时间)

算法图解:

代码示例:

python 复制代码
import threading
from queue import Queue

class Node:
    def __init__(self, value):
        self.value = value
        self.marked = False
        self.references = []

    def add_reference(self, node):
        self.references.append(node)

class ConcurrentGarbageCollector:
    def __init__(self):
        self.objects = []
        self.root_set = []
        self.lock = threading.Lock()
        self.work_queue = Queue()
        self.stop_event = threading.Event()

    def create_object(self, value):
        obj = Node(value)
        with self.lock:
            self.objects.append(obj)
        return obj

    def add_root(self, obj):
        with self.lock:
            self.root_set.append(obj)

    def mark_worker(self, worker_id):
        while not self.stop_event.is_set() or not self.work_queue.empty():
            try:
                obj = self.work_queue.get(timeout=0.1)
                if obj.marked:
                    self.work_queue.task_done()
                    continue
                obj.marked = True
                for ref in obj.references:
                    if not ref.marked:
                        self.work_queue.put(ref)
                self.work_queue.task_done()
            except:
                continue

    def concurrent_mark(self, num_workers=4):
        # 初始化标记阶段
        with self.lock:
            for obj in self.objects:
                obj.marked = False
            # 将根对象加入队列
            for root in self.root_set:
                self.work_queue.put(root)
        # 创建工作线程
        workers = []
        for i in range(num_workers):
            worker = threading.Thread(target=self.mark_worker, args=(i,))
            worker.start()
            workers.append(worker)
        # 等待标记完成
        self.work_queue.join()
        self.stop_event.set()
        for worker in workers:
            worker.join()
        self.stop_event.clear()

    def sweep(self):
        unreachable = []
        with self.lock:
            for obj in self.objects:
                if not obj.marked:
                    unreachable.append(obj)
            # 从对象列表中移除不可达对象
            for obj in unreachable:
                self.objects.remove(obj)
        return unreachable

    def collect(self):
        print("开始并发标记...")
        self.concurrent_mark()
        print("标记完成,开始清除...")
        collected = self.sweep()
        print(f"清除完成,回收了 {len(collected)} 个对象")

if __name__ == "__main__":
    gc = ConcurrentGarbageCollector()
    # 创建一些对象
    obj1 = gc.create_object("Object 1")
    obj2 = gc.create_object("Object 2")
    obj3 = gc.create_object("Object 3")
    obj4 = gc.create_object("Object 4")
    obj5 = gc.create_object("Object 5")
    # 构建引用关系
    obj1.add_reference(obj2)
    obj2.add_reference(obj3)
    obj3.add_reference(obj4)
    # 设置根对象
    gc.add_root(obj1)
    # obj5没有被任何对象引用,但它是根对象
    gc.add_root(obj5)
    # 创建一些循环引用
    obj6 = gc.create_object("Object 6")
    obj7 = gc.create_object("Object 7")
    obj6.add_reference(obj7)
    obj7.add_reference(obj6)
    print("垃圾收集前对象数量:", len(gc.objects))
    gc.collect()
    print("垃圾收集后对象数量:", len(gc.objects))
相关推荐
微风粼粼2 小时前
程序员在线接单
java·jvm·后端·python·eclipse·tomcat·dubbo
掘金-我是哪吒5 小时前
分布式微服务系统架构第158集:JavaPlus技术文档平台日更-JVM基础知识
jvm·分布式·微服务·架构·系统架构
abigalexy6 小时前
深入JVM底层-内存分配算法
jvm
weixin_ab18 小时前
JMM--数据原子操作
jvm
超级小忍19 小时前
JVM 中的垃圾回收算法及垃圾回收器详解
java·jvm
喝可乐的布偶猫1 天前
Java类变量(静态变量)
java·开发语言·jvm
麦兜*2 天前
Spring Boot启动优化7板斧(延迟初始化、组件扫描精准打击、JVM参数调优):砍掉70%启动时间的魔鬼实践
java·jvm·spring boot·后端·spring·spring cloud·系统架构
真实的菜2 天前
JVM类加载系统详解:深入理解Java类的生命周期
java·开发语言·jvm
在未来等你2 天前
JVM调优实战 Day 15:云原生环境下的JVM配置
java·jvm·性能优化·虚拟机·调优