Golang——内存(内存管理、内存逃逸、垃圾回收 (GC) 机制)
大家好,这里是编程Cookbook。本文详细介绍Go内存相关的内容,包括内存管理、内存逃逸、垃圾回收 (GC) 机制的三色标记,写屏障。
文章目录
- Go 的内存管理
- mspan && arenas
- arenas
- mspan
- mcache
- mcentral
- mheap
- 内存分配逻辑
- 内存回收逻辑
- 优势
- 内存碎片
- 内部碎片
- 外部碎片
- Go 的内存逃逸
- 什么是内存逃逸?
- 1. 栈 (Stack)
- 2. 堆 (Heap)
- 内存逃逸的检测:逃逸分析
- 典型的内存逃逸
- 内存逃逸的影响
- 优化建议
- 总结
- Go 的垃圾回收 (GC) 机制
- 特点
- 三色标记法工作原理
- 垃圾回收的触发条件
- 1. 手动触发
- 2. 自动触发
- GC 的影响与优化
- Go GC 流程
- 1. Stop-the-World (STW)
- 2. 标记 (Mark)
- 3. 标记阶段结束的 STW
- 4. 清理 (Sweep)
- 5. Start-the-World (STW 结束)
- 非分代回收 (Non-generational GC)
- 分代回收
- 为什么 Go 不使用分代回收?
- 非分代回收缺点
- Go 的写屏障 (Write Barrier)
- 写屏障是什么?
- 为什么需要写屏障?
- 混合写屏障策略
- 插入屏障 (Insertion Barrier)
- 删除屏障 (Deletion Barrier)
- 混合写屏障 (Hybrid Write Barrier)
- 混合写屏障的特点
- 混合写屏障的规则
- 1. 目标:确保三色不变性 (Tri-color Invariant)
- 2. 操作逻辑
- 写屏障的性能优化
- 写屏障的作用
- 写屏障的局限性
Go 的内存管理
Go 借鉴了 Google 的 TCMalloc(高性能的、用于 C++ 的内存分配器)。其核心思想是 内存池 + 多级对象管理,能加快分配速度,降低资源竞争。
在 Go 里用于内存管理的对象结构主要是:arenas && mspan、mcache、mcentral、mheap。
- mspan 是一个基础结构,分配内存时,基本以它为单位,并通过 arena 管理更大范围的内存【每级都是多个 mspan 组成】。
mheap
、mcentral
和mcache
起到了多级内存池的作用,当有对应大小的对象需要分配时会先到它们这一层请求。- 如果当前层内存池不够用时,会按照【mcache -> mcentral -> mheap -> 操作系统】顺序一层一层地往上申请内存。
在 Go 内存管理中,构成了多层次的内存管理架构。它们分别负责不同级别的内存分配需求,从全局分配到本地缓存,确保内存分配高效且低竞争。
- mcache: 本地缓存,为
P
提供快速分配的内存块。- mcentral: 中间管理层,按
sizeclass
分类管理mspan
。- mheap: 全局内存管理器,负责大对象分配和向操作系统申请内存。
三者之间的分层设计,使得 Go 的内存管理既高效又灵活。
总体上来看,Go 内存管理也是一个金字塔结构:
mspan && arenas
以下图像部分来源于:golang 重要知识:内存管理
先来看看 mspan 这个基础结构体。首先,当 Go 在程序初始化的时候,会将申请到的虚拟内存划分为以下三个部分:
总体上来讲,spans 和 bitmap 区域可以看作是 arenas 区域的元数据信息,辅助内存管理。
- arenas:
- 表示内存池,划分为连续的页面(8KB 为单位)。
- spans:
- 管理一组连续页面,作为分配内存的单位。
- bitmap:
- 记录 arena 的分配状态和 GC 信息,辅助垃圾回收。
arenas
arenas: 动态分配的堆区,它将分配到的内存以 8k 为一页进行管理。
mspan
arenas这个单位还是太细了,因此再抽象出 mspan 这一层来管理arenas。mspan 记录了这组连续页面的起止地址、页数量、以及类型规格。
关于 mspan 的类型规格有 67 种,每一种都被定义了一个固定大小,当有对象需要分配内存时,就会挑选合适规格的 mspan 分配给对象。
mcache
mcache 是 Go 中为每个逻辑处理器(P
)分配的本地内存缓存。每个 P
拥有一个独立的 mcache
。
- alloc: 缓存不同规格的
mspan
,用于快速分配内存。 - tiny: 提供微型分配器,用于分配 ≤16B 的小对象。
作用
- 本地内存分配:
- 优先从
mcache
分配内存,避免线程间的竞争。
- 优先从
- 快速分配:
mcache
是运行时的最底层内存分配器,分配速度最快。
- 减少锁开销:
mcache
的分配无需加锁,因为每个P
独占一个mcache
。
mcentral
mcentral 是 Go 中用于管理不同规格(sizeclass
)的 mspan
的中间层。它负责为 mcache 提供内存块(mspan
)。
作用
- 分级管理:
mcentral
会按 sizeclass 将mspan
分为 67 种规格。
- 分配内存:
- 当本地缓存(
mcache
)的内存不足时,会向mcentral
请求新的mspan
。
- 当本地缓存(
- 减少竞争:
- 通过分级管理,避免了对不同大小内存的分配竞争。
mheap
mheap 是 Go 内存管理的全局对象,负责向操作系统申请内存,并管理大块的内存区域。它是整个内存分配系统的顶层。
作用
- 大对象分配:
- 当对象大小超过 32KB 时,直接从
mheap
分配内存。
- 当对象大小超过 32KB 时,直接从
- 内存来源:
- 当
mcentral
或mcache
需要新的内存时,会向mheap
请求。
- 当
- 内存管理:
- 管理所有的 arenas 和大块的内存区域。
- 提供空闲的 span 给下级(
mcentral
和mcache
)。
内存分配逻辑
- 如果
object size > 32KB
,则直接使用 mheap 来分配空间,mheap
中有一个 freelarge 字段管理着超大 span; - 如果
object size < 16Byte
,则通过 mcache 的 tiny 分配器来分配(tiny
可看作是一个指针 offset); - 如果
object size
在上述两者之间,首先尝试通过 sizeclass 对应的分配器分配; - 如果 mcache 的分配器没有空闲的 span 分配,则向 mcentral 申请空闲块;
- 如果 mcentral 也没空闲块,则向 mheap 申请并进行切分;
- 如果 mheap 也没合适的 span,则向系统申请新的内存空间。
内存回收逻辑
- 如果
object size > 32KB
,直接将 span 返还给 mheap 的自由链; - 如果
object size < 32KB
,查找 object 对应 sizeclass,归还到 mcache 自由链; - 如果 mcache 自由链过长或内存过大,将部分 span 归还到 mcentral;
- 如果某个范围的 mspan 都已经归还到 mcentral,则将这部分 mspan 归还到 mheap 页堆;
- mheap 不会定时将内存归还到系统,但会归还虚拟地址到物理内存的映射关系,当系统需要的时候可以回收这部分内存,否则暂时先留着给 Go 使用。
优势
-
用户态完成分配:
- 内存分配大多在用户态完成,不需要频繁进入内核态。
-
快速分配,优化碎片管理:
- 本地缓存 (
mcache
) 无需加锁,分配效率极高。 - Go 自己在用户态管理内存,固定大小分类(67 种
sizeclass
)减少碎片。
- 本地缓存 (
-
减少CPU 竞争,支持高并发:
- 每个逻辑处理器 (
P
) 独享本地缓存,避免线程间锁竞争。 - 按
sizeclass
分类分配,降低资源冲突,适合处理大量 goroutine。
- 每个逻辑处理器 (
内存碎片
系统在内存管理过程中产生的一种现象,表现为无法被有效利用的内存空间。解决内存碎片问题是内存管理优化的重要目标。分为 内部碎片 和 外部碎片。
- 内部碎片: 分配的内存大于实际需求,导致浪费。
- 外部碎片: 小块空闲内存分散分布,难以利用。
内部碎片
- 分配给进程的内存区域中,有些部分未被使用。
- 原因:
- 字节对齐:为了满足字节对齐的要求,会额外分配一些内存空间,但这些空间未被实际使用。
- 固定大小分配:申请 28B 内存时,系统可能按 32B 的单位分配,多出的 4B 即为浪费。
- 特点:
- 内部碎片主要表现为分配的内存大于实际需求,但超出的部分不能被利用。
外部碎片
- 内存中存在一些小的空闲分区,这些分区没有被分配给任何进程,但由于过小,难以被再次利用。
- 原因:
- 内存反复分配和释放,导致小块内存分布在内存的各个位置。
- 空闲的内存块难以合并成更大的内存区域。
- 特点:外部碎片导致内存空间虽然有剩余,但因为分布不连续,无法满足大块内存的分配需求。
Go 的内存逃逸
什么是内存逃逸?
- 定义: 当一个对象从 栈 中逃逸到 堆 中分配内存时,就称为内存逃逸。
- 结果:
- 栈内存对象在函数结束时会被自动回收。
- 堆内存对象需要垃圾回收器(GC)处理。
1. 栈 (Stack)
- 特点:
- 线性存储,由编译器自动分配和回收。
- 用于存储函数参数和局部变量。
- 优点:
- 内存分配和释放的代价极低,仅需两条指令(
PUSH
和POP
)。 - 内存生命周期与函数执行周期一致,无需 GC 介入。
- 内存分配和释放的代价极低,仅需两条指令(
- 缺点:
- 受限于栈的大小,生命周期有限。
2. 堆 (Heap)
- 特点:
- 用于动态分配,存储生命周期不确定的对象。
- 内存由垃圾回收器(GC)管理。
- 优点:
- 适合存储生命周期超出函数范围的对象。
- 缺点:
- 分配和回收的代价较高,依赖 GC,会增加性能开销。
内存逃逸的检测:逃逸分析
-
逃逸分析:
编译器在编译阶段对变量生命周期进行分析,决定变量分配在堆还是栈。JVM 则是运行时逃逸分析;
程序变量会携带有一组校验数据,检查变量的生命周期是否是完全可知的,如果通过检查,则可以在栈上分配。否则,就是所谓的逃逸,必须在堆上进行分配。
-
分析准则:
- 如果变量在函数外部没有引用,则分配在栈中。
- 如果变量在函数外部存在引用,则分配在堆中。
- 栈上分配更高效,不需要 GC 处理。
- 堆上分配适用于生命周期不可知或较长的变量。
典型的内存逃逸
以下是容易导致变量逃逸到堆的情况:
- 返回局部变量的指针。
- 指针或带指针的值被发送到
channel
。 - 切片中存储指针。
- 切片的底层数组因扩容而重新分配。
- 使用
interface
类型或动态分配内存。
-
指针返回:
- 函数内定义的局部变量通过指针返回:
func escape() *int {x := 10return &x // x 逃逸到堆中 }
- 函数内定义的局部变量通过指针返回:
-
指针发送到 channel:
- 变量的指针或带指针的值被发送到
channel
中:go func(ch chan *int) {val := 10ch <- &val // val 逃逸到堆中 }(ch)
- 变量的指针或带指针的值被发送到
-
切片存储指针:
- 切片中存储指针值导致背后数组的内存逃逸:
func example() {strs := []*string{}val := "hello"strs = append(strs, &val) // val 逃逸到堆中 }
- 切片中存储指针值导致背后数组的内存逃逸:
-
切片扩容:
- 切片的容量动态扩展,导致新的内存分配到堆:
func example() {slice := make([]int, 2)slice = append(slice, 1) // 扩容时,背后的数组逃逸到堆中 }
- 切片的容量动态扩展,导致新的内存分配到堆:
-
动态类型调用:
-
使用
interface
类型时,接口的动态方法调用使得编译器无法确定对象的生命周期。会导致内存逃逸:io.Reader 是接口类型func readExample(r io.Reader) {buf := make([]byte, 10)r.Read(buf) // buf 逃逸到堆中 }
-
内存逃逸的影响
-
优点:
- 提供灵活性,支持生命周期不确定或超出函数范围的对象。
-
缺点:
- 增加垃圾回收压力,降低程序性能。
- 堆分配的内存需要更多的系统资源和管理成本。
优化建议
-
减少不必要的指针传递:
- 避免使用指针返回局部变量,改为直接返回值。
-
减少动态分配:
- 使用固定大小的数组或切片,避免频繁扩容。
-
优化
interface
使用:- 尽量使用具体类型,减少
interface
动态分配。
- 尽量使用具体类型,减少
总结
- 内存逃逸 是 Go 内存分配过程中,为了确保变量生命周期正确而将其从栈移到堆的现象。
- 编译器通过 逃逸分析 决定变量是否需要从栈逃逸到堆。
- 堆分配虽然提供灵活性,但增加了性能开销,应尽量避免不必要的逃逸。
- 在性能敏感的代码中,通过值传递和优化动态分配可以有效减少逃逸的发生。
Go 的垃圾回收 (GC) 机制
GC(Garbage Collection)垃圾回收是一种自动管理内存的方式,无需手动管理内存,程序能够检测和清除不再被使用的内存块,避免内存泄漏,使开发人员从内存管理上解脱出来。
Go 语言内置了一套现代化、高效的垃圾回收 (Garbage Collection, GC) 机制,用于自动管理内存分配和释放。以下是 Go GC 的详细介绍:
特点
-
并发 (Concurrent):
- Go 的 GC 与程序的其他部分同时运行,不会完全阻塞程序的执行。
- 标记阶段主要部分是并发的,通过写屏障确保标记的正确性。
- 清理阶段默认是并发,极少使用串行清理;
-
三色标记清除算法 (Tri-color Mark and Sweep):
- Go 使用三色标记清除算法,是一种高效的垃圾回收技术。
-
非分代回收 (Generational):
- Go 的 GC 没有像传统分代回收那样将对象分为不同代,如新生代(存放短生命周期对象)和老年代(存放长期存在的对象)等。
-
可配置性:
- 开发者可以通过 GOGC 环境变量调整垃圾回收器的灵敏度。
-
写屏障:
- Go 的GC使用混合写屏障策略,在标记阶段与程序并发运行,同时保持标记的准确性和一致性。
三色标记法工作原理
-
初始状态:
- 所有对象都被标记为白色。
- 根对象(全局变量、栈上的变量等)被标记为灰色。
-
标记过程:
- 遍历灰色对象,并将其引用的对象标记为灰色,直到没有灰色对象。
- 将已处理的灰色对象标记为黑色。
-
清理过程:
- 删除仍为白色的对象,因为这些对象不可达。
垃圾回收的触发条件
1. 手动触发
- 使用
runtime.GC()
主动触发垃圾回收。
2. 自动触发
- Go 的 GC 会根据内存使用情况自动触发。
- GOGC 环境变量控制触发频率:
- 在申请内存的时候,检查当前已分配的内存是否大于上次GC后的内存的2倍(默认)。
- 监控线程(sysmon)检测到自上次 GC 已超过一定时间(例如两分钟),它会触发一次 GC。
GC 的影响与优化
-
优点:
- 简化内存管理,减少内存泄漏。
- 自动化管理内存,开发者不需要手动释放内存。
- 支持并发和实时性,适合高并发场景。
-
缺点:
- 存在一定的性能开销。
- 对延迟敏感的程序可能会受到影响。
-
优化方法:
- 优化内存分配: 减少短生命周期对象的创建,避免频繁触发 GC。
- 减少逃逸: 使用值类型替代指针,尽量避免变量逃逸到堆中。
- 调整 GOGC: 根据场景调整 GOGC 值以平衡性能和内存使用。
Go GC 流程
- Stop-the-World: 暂停所有业务逻辑,确保初始状态的一致性。
- 标记: 并发标记存活对象,标记过程与应用程序同时运行(并发标记,需要用到写屏障)。
- 再次Stop-the-World: 在标记阶段的最后,GC 会短暂地暂停程序,重新扫描根对象和写屏障记录的变化,确保标记的准确性。
- 清理: 回收未标记的对象,清理可以并发或串行完成。
- Start-the-World: 恢复业务逻辑执行。
STW(Stop-the-World)仅用于标记的开始和结束阶段。标记阶段后需要一个非常短暂的 Stop-the-World 来确保标记阶段的一致性和完成状态。确保标记过程中的所有写屏障操作都已处理。
清理阶段(Sweep)通常是并发完成的,不需要 STW。
1. Stop-the-World (STW)
-
目的:
- 确保垃圾回收器(GC)在标记阶段能够准确扫描所有活跃对象。
- 阻止新的对象分配干扰标记过程。
-
具体操作:
- 设置
gcwaiting=1
,通知所有 M(系统线程)进入休眠状态。 - 通过让当前运行中的 G(goroutine)完成或中断任务,确保所有 M 都暂停。
- 设置
-
解释:
- Modern Go (>= 1.5) 使用了并发 GC,尽量缩短 STW 的时间。在并发标记清除的算法下,STW 时间通常仅限于标记过程的开始和结束,而标记本身是在并发阶段完成的。
2. 标记 (Mark)
-
目的:
- 找出程序中仍然存活的对象,并标记它们为活跃。
-
过程:
- 分配任务:
- 将标记任务分成若干段,分配给
gcproc
个 M(系统线程),其中gcproc
的默认值为逻辑处理器 P 的数量。 - 每个 M 在被唤醒后,检查自身的
helpgc
标记是否为true
,如果是,则开始执行标记任务。
- 将标记任务分成若干段,分配给
- 并发标记:
- 当前运行的 M 和其他被唤醒的 M 会并发执行标记任务。
- 如果某个 M 的任务完成,会从其他未完成任务的 M 中“偷取”标记任务,直到所有标记任务完成。
- 进入休眠:
- 标记任务完成后,所有参与 GC 的 M 再次进入休眠状态。
- 分配任务:
-
解释:
- Go 的三色标记法(白、灰、黑)用于跟踪对象引用状态。
- 标记阶段尽量减少对程序逻辑的影响,大部分标记任务是在并发模式下完成的。
3. 标记阶段结束的 STW
-
目的:
- 确保标记阶段中所有引用的动态变化都被正确处理。
处理
通过写屏障收集的对象引用变更。
-
过程:
- 在标记阶段,应用程序可能继续运行,导致对象的引用关系发生变化。
- 写屏障 记录了这些动态变更。
- 在标记结束时,STW 确保所有这些变更被应用,保证标记的最终一致性。
-
特点:
- 这一 STW 时间非常短,通常只需数十微秒到几毫秒。
- 这是现代 Go (>= 1.5) 的重要优化点,尽量将 STW 时间压缩到最小。
4. 清理 (Sweep)
- 目的:
- 回收未被标记的对象所占用的内存。
- 过程:
- 清理阶段可以选择串行或并发执行:
- 并发清理: 通过单独的 Goroutine 执行,不需要阻塞业务逻辑(Go >= 1.3 默认行为)。
- 串行清理: 清理任务与主 GC 线程绑定,可能导致较长的 STW 时间。
- 清理阶段可以选择串行或并发执行:
- 解释:
- 并发清理的引入极大地降低了 GC 对业务逻辑的影响。
- 清理阶段主要释放未标记的内存,回收至内存池。
5. Start-the-World (STW 结束)
- 目的:
- 恢复业务逻辑执行,唤醒所有线程(最多等于 P 的数量)。
- 过程:
- 设置
gcwaiting=0
,通知所有的 M 线程业务逻辑恢复正常。 - 唤醒 P 个 M,继续调度执行业务逻辑中的 G。
- 设置
- 解释:
- Start-the-World 阶段保证垃圾回收完成后程序能够立即恢复。
非分代回收 (Non-generational GC)
Go 不是分代回收 (Generational GC),而是采用了 非分代回收 (Non-generational GC) 策略。即:
-
不区分对象的生命周期,对所有对象一视同仁地标记和回收。
-
强调简洁、高效和低延迟的实现。
-
虽然非分代回收可能在短生命周期对象的处理上效率不如分代回收,但 Go 的整体设计使其在并发、高性能场景中表现优异。
分代回收
分代回收是一种常见的垃圾回收优化技术,它将内存中的对象按照生命周期的长短分成不同的“代”,通常包括:
- 年轻代: 短生命周期的对象,回收频率高。
- 老年代: 长生命周期的对象,回收频率低。
- 持久代: 很少或几乎不会被回收的对象(如类元数据)。
核心思想:
- 大多数对象会很快变成垃圾(“弱代假设”)。
- 针对短生命周期和长生命周期的对象,采用不同的回收策略可以提高性能。
分代回收的典型代表是 Java 的 JVM 的垃圾回收器。
为什么 Go 不使用分代回收?
-
简化实现:
- 分代回收需要额外的数据结构和复杂性来管理不同代的内存区域,而 Go 的设计目标是简洁高效。
-
内存模型不同:
- Go 的并发模型(大量 Goroutine 和高频率的内存分配)使得分代回收的复杂性和性能开销可能得不偿失。
-
实时性需求:
- Go 强调低延迟、实时性,非分代回收的设计让 Go 更容易实现垃圾回收与业务逻辑的并发执行。
非分代回收缺点
- 短生命周期对象的回收成本较高:
- 对于短生命周期的对象,仍然需要完整扫描,增加了一定的性能开销。
- 内存使用效率可能稍低:
- 无法像分代回收那样对不同生命周期的对象采用优化策略。
Go 的写屏障 (Write Barrier)
写屏障是现代垃圾回收器中的一项关键技术,用于在垃圾回收的 并发标记阶段 追踪程序运行时对象引用的动态变化。Go 的垃圾回收器使用 混合写屏障 策略,这种设计能够在标记阶段与程序并发运行,在保证 GC 效率和正确性的同时,将 Stop-the-World (STW) 的时间压缩到最短,这是 Go 高效 GC 的重要基础。
写屏障是什么?
写屏障是一种机制,当程序修改对象的引用关系(即写入内存)时,写屏障会执行额外的操作,确保垃圾回收器能够正确地跟踪这些引用的变化。
- Go 的写屏障特点:
- 采用 混合写屏障 策略,结合插入屏障和删除屏障。
- 确保三色不变性,防止白色对象被错误回收。
- 仅在标记阶段启用,并发运行,与程序逻辑干扰最小。 在清理阶段或非 GC 期间无影响。
为什么需要写屏障?
Go 的垃圾回收 (GC) 使用 并发标记 (Concurrent Marking),在标记阶段,垃圾回收器和应用程序会同时运行。这种并发运行可能导致程序修改对象的引用关系,从而产生潜在问题,如下:
应用程序和垃圾回收器可以同时运行,因此对象确实可以在标记阶段被修改。我们考虑一下,下面的情况:
我们在进行三色标记中扫描灰色集合中,扫描到了对象 A,并标记了对象 A 的 所有引用,这时候,开始扫描对象 D 的引用,而此时,另一个 goroutine 修改 了 D->E 的引用,变成了如下图所示:
这样会不会导致 E 对象就扫描不到了,而被误认为为白色对象,也就是垃圾。
写屏障就是为了解决这样的问题,引入写屏障后,在上述步骤后,E 会被认为 是存活的,即使后面 E 被 A 对象抛弃,E 会被在下一轮的 GC 中进行回收,这一 轮 GC 中是不会对对象 E 进行回收的。
混合写屏障策略
Go 的写屏障实现采用了 混合写屏障 (Hybrid Write Barrier) 策略。这种设计结合了两种经典的写屏障策略:插入屏障 (Insertion Barrier) 和 删除屏障 (Deletion Barrier)。
- 混合屏障:
- 插入屏障: 确保新加入的引用不会丢失,目标对象被正确标记。
- 删除屏障: 记录被移除的旧引用,防止对象错误回收。
- Go 的混合写屏障在实现时优先基于插入屏障的逻辑进行优化,而删除屏障的逻辑则只在特定情况下被补充实现。
插入屏障 (Insertion Barrier)
插入屏障的作用是在 对象引用发生新增时,对新引用的对象执行某些操作,以确保它们不会被错误回收。
工作原理:
- 当一个对象的引用被修改为指向另一个对象(新增引用)时,插入屏障会立即将新引用的对象从白色(未标记)变为灰色(待标记)。
- 插入屏障保证了新加入的对象会被正确处理。
优点:
- 保证新增的引用关系不会丢失。
- 简化了垃圾回收器的实现逻辑。
缺点:
- 可能无法处理对象引用被移除的场景。
删除屏障 (Deletion Barrier)
删除屏障的作用是在 对象引用被移除时,对被移除的对象(尤其是白色和灰色对象)执行某些操作,以确保它们在标记阶段被正确处理。
移除引用的对象不一定是需要回收的,它们可能仍然是存活的对象,只是被移除了当前的引用关系。
工作原理:
- 当对象的引用被移除时,删除屏障会记录下被移除的对象,确保这些对象在当前回收周期中不会被错误地处理或遗漏。
- 删除屏障强调保护“旧引用”。
如果被移除引用的对象是白色
(未标记):
- 删除屏障会立即
将该对象从白色变为灰色
。- 目的:确保该对象在标记阶段被进一步处理,而不是被错误回收。
如果被移除引用的对象是灰色(已访问但其引用未完全处理):
- 删除屏障保持灰色状态不变。
- 目的:确保 GC 正确完成对灰色对象的递归标记。
如果被移除引用的对象是黑色(已完全处理):
- 删除屏障无需任何额外操作。
- 目的:黑色对象已经完全处理,不会受引用变化的影响。
优点:
- 能够处理对象引用被移除的场景。
- 防止白色对象被错误回收。
缺点:
- 当引用频繁新增和移除时,性能开销较大。
混合写屏障 (Hybrid Write Barrier)
混合写屏障结合了插入屏障和删除屏障的特点,能够同时处理 引用新增 和 引用移除 的场景。Go 的写屏障实现采用了这种策略。
工作原理:
- 新增引用:
- 当一个对象的引用被修改为指向新对象时,插入屏障逻辑会确保新引用的对象被标记为灰色,避免漏标。
- 移除引用:
- 当一个对象的引用被移除时,删除屏障逻辑会确保被移除的对象不会被错误回收。
混合写屏障的特点
优点:
- 完整性:
- 同时处理新增和移除引用,保证三色标记算法的正确性。
- 性能优化:
- 优化了插入屏障的逻辑,在标记阶段优先处理新增引用。
- 对删除屏障的逻辑只在必要时执行,避免了不必要的性能开销。
- 只对三色标记阶段生效。
缺点:
- 写屏障的逻辑复杂度略高,需要额外处理插入和删除两种场景。
- 增加了一定的内存写操作开销。
混合写屏障的规则
1. 目标:确保三色不变性 (Tri-color Invariant)
- 三色标记法中对象的三种状态:
- 白色:尚未访问,可能是垃圾。
- 灰色:已访问但其引用未完全处理。
- 黑色:已访问且其引用已完全处理。
- 写屏障的任务是保证标记过程中,白色对象不会因为程序的引用变更被错误遗漏。
2. 操作逻辑
- 当某个对象的引用被修改时:
- 如果目标对象是白色,将目标对象标记为灰色(放入灰色队列)。
- 确保修改后的引用关系被正确追踪。
写屏障的性能优化
写屏障会引入一定的性能开销,但 Go 的实现通过以下方式优化性能:
-
启用条件:
- 写屏障仅在 GC 的标记阶段启用,在非 GC 阶段没有额外开销。
-
批量标记:
- 写屏障会尽量以批量方式处理引用变更,减少对性能的影响。
-
内存屏障优化:
- 写屏障逻辑使用轻量级的原子操作,最大程度降低对应用程序性能的干扰。
-
只处理必要对象:
- 只有在标记阶段,且目标对象是白色时,写屏障才会执行额外操作,减少了不必要的屏障逻辑。
写屏障的作用
-
保证三色不变性:
- 避免白色对象被程序动态引用后未被标记,确保对象不会被错误回收。
-
减少 Stop-the-World 时间:
- 通过写屏障捕获动态引用,GC 无需在 STW 阶段完全重新扫描内存。
-
支持并发标记:
- 写屏障与并发标记结合,提升了垃圾回收器的效率。
写屏障的局限性
-
性能开销:
- 写屏障逻辑会增加内存写操作的开销,尤其是在高频引用修改的场景下。
-
复杂性:
- 写屏障的实现需要保证低延迟,同时处理动态引用的复杂性。
相关文章:
Golang——内存(内存管理、内存逃逸、垃圾回收 (GC) 机制)
大家好,这里是编程Cookbook。本文详细介绍Go内存相关的内容,包括内存管理、内存逃逸、垃圾回收 (GC) 机制的三色标记,写屏障。 文章目录 Go 的内存管理mspan && arenasarenasmspan mcachemcentralmheap内存分配逻辑内存回收逻辑优势内…...
关系型数据库的技术思路
一、网络协议 需要根据TCP协议设计一个客户端和服务器之间的命令响应协议。 1、服务端回复声明 2、客户端发送登录包 3、服务端返回登录结果 4、登录成功后进入命令阶段,否则退出。 二、每个连接用一个线程 服务器为每个客户端连接开启一个线程。 三、需要文件的随机…...
web安全——分析应用程序
文章目录 一、确定用户输入入口点二、确定服务端技术三、解析受攻击面 一、确定用户输入入口点 在检查枚举应用程序功能时生成的HTTP请求的过程中,用户输入入口点包括: URL文件路径 通常,在查询字符?之前的URL部分并不视为用户输入入口&am…...
数据库事务的基本要素(ACID)
数据库事务的基本要素(ACID) 在数据库管理系统(DBMS)中,事务(Transaction)是一个或多个数据库操作的集合,这些操作要么全部成功,要么全部失败。事务的目标是保证数据的一…...
server.servlet.session.timeout: 12h(HTTP 会话的超时时间为 12 小时)
从你提供的配置文件(应该是 Spring Boot 的 application.yml 或 application.properties 文件)来看,以下部分与会话超时时间相关: server:servlet:session:timeout: 12h # timeout: 30cookie:name: VENDER_SID会话超时时间的…...
Docker数据卷容器实战
数据卷容器 数据共享 上面讲述的是主机和容器之间共享数据,那么如何实现容器和容器之间的共享数据呢?那就是创建 创建数据卷容器。 命名的容器挂载数据卷,其他容器通过挂载这个(父容器)实现数据共享,挂载…...
基于LangChain4j调用火山引擎DeepSeek R1搭建RAG知识库实战指南
基于LangChain4j调用火山引擎DeepSeek R1搭建RAG知识库实战指南 基于LangChain4j调用火山引擎DeepSeek R1搭建RAG知识库实战指南 基于LangChain4j调用火山引擎DeepSeek R1搭建RAG知识库实战指南一、注册火山引擎账号二、RAG技术核心原理三、环境与工具准备1. 核心组件2. 依赖配…...
Java内存的堆(堆内、堆外)、栈含义理解笔记
一、核心概念区分 1. 内存中的堆(Heap)与栈(Stack) 栈内存 ▸ 用途:存储方法调用、局部变量、基本类型数据(如 int a 1) ▸ 特点: 线程私有,每个线程独立分配栈空间。…...
2.7 大模型RAG内容安全合规检查-大模型ACP模拟题-真题
单选题 在RAG应用中,输入内容合规检查的正确顺序是? A. 先处理模型生成,后检查用户输入 B. 先检查用户输入,后处理模型生成 ✅ C. 仅检查用户输入 D. 仅检查模型输出 解析:合规流程应优先过滤用户输入风险࿰…...
C#从入门到精通(35)—如何防止winform程序因为误操作被关闭
前言: 大家好,我是上位机马工,硕士毕业4年年入40万,目前在一家自动化公司担任软件经理,从事C#上位机软件开发8年以上!我们在开发的上位机软件运行起来以后,一般在右上角都有一个关闭按钮,正常情况下点击关闭按钮就能关闭软件,但是不排除我们不想关闭软件,但是因为不…...
特斯拉 FSD 算法深度剖析:软件层面全解读
一、引言 特斯拉的 FSD(Full Self-Driving)系统作为自动驾驶领域的前沿成果,其软件层面的算法设计至关重要。本文将从软件的角度,深入探讨特斯拉 FSD 所采用的算法,包括感知、规划、控制等多个方面,以期为…...
中央一号文件里的三维革命:数字孪生如何重构智慧乡村生态?
2024年中央一号文件提出"建设宜居宜业和美乡村",这一目标背后离不开数字技术的支撑。在浙江某数字乡村试点,凡拓数创通过三维建模还原整村风貌,路灯能耗、垃圾分类、古建保护等数据在虚拟空间中实时跳动。管理人员坦言:…...
DeepSeek 助力 Vue3 开发:打造丝滑的页眉(Header)
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…...
一个原教旨的多路径 TCP
前面提到过 ECMP 和 TCP 之间的互不友好,pacing 收益和中断开销的互斥,在事实上阻碍了 packet-based LB 的部署,也限制了交换机,服务器的并发性能,同时潜在增加了 bufferbloat 的概率,而适用 packet-based …...
DDD 架构之领域驱动设计【通俗易懂】
文章目录 1. 前言2. MVC 对比 DDD3. DDD 分层架构4. 完整业务流程 1. 前言 官方回答:DDD是一种应对复杂业务系统的设计方法,通过将软件设计与业务领域紧密结合,帮助开发人员构建清晰、可维护的领域模型。在复杂的业务系统中,它能…...
蓝桥杯15届JavaB组6题
一开始用的dfs,但是好像是因为数据量太大,数据错误,而且会超时,然后使用bfs dfs的代码(自留): import java.util.*;public class F15 {public static void main(String[] args) {Scanner sc n…...
Python 爬虫与网络安全有什么关系
Python爬虫和网络安全之间存在密切的关系。爬虫是一种用于自动化从网络上获取信息的程序,而网络安全是保护计算机网络和系统免受未经授权的访问、攻击和数据泄露的实践。本文将探讨Python爬虫与网络安全之间的关系以及如何在爬虫开发中注意网络安全。 爬虫的作用和…...
如何判断数据是否使用AES加密以及如何使用AES解密
在现代软件开发中,数据加密是保护敏感信息的重要手段。AES(Advanced Encryption Standard)是一种广泛使用的对称加密算法,因其安全性和高效性而被广泛应用。本文将介绍如何判断数据是否使用AES加密,并详细讲解如何使用…...
C++ Primer 泛型算法结构
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
java给钉钉邮箱发送邮件
1.开通POP和IMAP 2.引入pom <dependency><groupId>javax.mail</groupId><artifactId>mail</artifactId><version>1.4.7</version> </dependency>3.逻辑 String host "smtp.qiye.aliyun.com"; String port "…...
交安ABC-公路水运工程企业安全员ABC如何报名?报名要哪些条件?
交安ABC的报名方式及报名条件如下: 报名方式 1. 企业注册:企业要进入交通运输部公路水运工程施工主要负责人和安全生产管理人员信息管理系统,注册企业账户,提交企业相关信息,填写企业资料。 2. 发送审核ÿ…...
软件测试丨Docker与虚拟机架构对比分析
Docker 与虚拟机(VM)在架构上有显著区别,主要体现在资源利用、性能、隔离性和启动时间等方面。以下是两者的主要架构区别: 1. 架构层次 Docker: 主机操作系统:Docker 直接运行在宿主机的操作系统上。Docker 引擎&…...
Gatling介绍
Gatling:基于Scala的高性能负载测试工具 一、Gatling 简介 Gatling 是一个开源的 HTTP 压力测试工具,专为高并发场景设计,支持 HTTP/HTTPS、WebSocket、Kafka 等协议。其基于 Scala 的脚本语法简洁高效,支持实时报告生成和分布式…...
Shot Studio for macOS 发布 1.0.2
Shot Studio 是一个 macOS 的 app,专门为开发者设计,主要用于各大 app 应用商店的预览图设计。 提供了非常多的模板,也预设了很多尺寸。可以直接一键使用 在 1.0.2 这个版本中新增了: 文本渐变 图层:边框、颜色、圆…...
基于Spring Boot的乡村养老服务管理系统设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
Harbor服务需要crt证书,而下载是nginx的证书pem,应该怎么处理
1. 明确文件用途 shgbitai.com.key:SSL 私钥文件(与 Apache/Nginx 通用)。shgbitai.com.pem:已合并的证书文件(通常包含域名证书 中间证书链)。 Nginx 的证书通常以 .pem 或 .crt 为后缀,而 …...
Hadoop第一课(配置linux系统)
1、让hadoop用户,有root权限,如果可以不输密码更好: (1)先登入root用户 (2)visudo命令进入配置文件 (3)找到 root ALL(ALL) ALL这一行,在该行下面增加:hadoop ALL(ALL) ALL (:set nu可以显示行号ÿ…...
Java+Vue+uniapp微信小程序校园自助打印系统(程序+论文+讲解+安装+调试+售后)
感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,我会一一回复,希望帮助更多的人。 系统介绍 在当今时代,网络与科学技术正以前所未有的速度迅猛发展,这股强大…...
QT 中的元对象系统(二):元对象实现原理QMetaObject
目录 1.元对象系统的构成 2.QObject和QMetaObject的关系 3.Qt 元对象模型QMetaObject 3.1.基本信息 3.2.类信息classinfo 3.3.类构造函数constructor 3.4.枚举信息 enumerator 3.5.类方法method 3.6.类属性peoproty 4.MOS(Meta Object System)示例 5.总结 1.元对象系…...
C++:指针函数与函数指针
文章目录 概述什么是指针函数(Pointer Function)什么是函数指针(Function Pointer)总结: 概述 什么是指针函数(Pointer Function) 指针函数是指返回类型为指针的函数。即,函数返回…...
用HTML5+CSS+JavaScript实现新奇挂钟动画
用HTML5+CSS+JavaScript实现新奇挂钟动画 引言 在技术博客中,如何吸引粉丝并保持他们的关注?除了干货内容,独特的视觉效果也是关键。今天,我们将通过HTML5、CSS和JavaScript实现一个新奇挂钟动画,并将其嵌入到你的网站中。这个动画不仅能让你的网站脱颖而出,还能展示你的…...
【MySQL篇】表的操作
1,创建表 语法: create table ( field1 datatype, field2 datatype, field3 datatype )charset 字符集 collate 校验规则 engine 存储引擎; 说明: field表示列名datatype表示列的类型charset字符集,如果没有指明,则…...
CCF-CSP历年真题大全附题解python
2023012的真题 202312-1 仓库规划 5415. 仓库规划 - AcWing题库高质量的算法题库https://www.acwing.com/problem/content/5418/ 解题思路: 其实就是对比(x.y,z.....)需要找到一个每个元素都大于这个坐标得坐标,本题可以直接…...
Mysql——约束与多表查询
一、约束 1.1定义 约束是对表中的数据进行限制的一套规则,用于防止用户向数据库中输入无效数据。它可以保证表中的数据满足特定业务规则和逻辑,从而维护数据的准确性和可靠性。 1.2作用 数据完整性 :约束可以确保数据在插入、更新或删除时符…...
如何看待 Kaiming He 最新提出的 Fractal Generative Models ?
何恺明团队提出的分形生成模型(Fractal Generative Models) 引发了广泛关注,其核心思想是通过递归调用生成模型模块构建自相似结构,类似数学中的分形概念(如雪花结构),从而高效生成高分辨率数据(如图像)。 Fractal Generative Models即分形生成模型,是一种新型的生成…...
【大模型系列篇】如何解决DeepSeek-R1结构化输出问题,使用PydanticAl和DeepSeek构建结构化Agent
今日号外:🔥🔥🔥 DeepSeek开源周:炸场!DeepSeek开源FlashMLA,提升GPU效率 下面我们开始今天的主题,deepseek官方明确表示deepseek-r1目前不支持json输出/function call,可…...
Metal 学习笔记五:3D变换
在上一章中,您通过在 vertex 函数中计算position,来平移顶点和在屏幕上移动对象。但是,在 3D 空间中,您还想执行更多操作,例如旋转和缩放对象。您还需要一个场景内摄像机,以便您可以在场景中移动。 要移动…...
Android-创建mipmap-anydpi-v26的Logo
利用 Android Studio 自动创建 创建新项目:打开 Android Studio,点击 “Start a new Android Studio project” 创建新项目。在创建项目的过程中,当设置Target SDK Version为 26 或更高版本时,Android Studio 会在项目的res目录下…...
DOM 事件 HTML 标签属性速查手册
以下是一份 DOM 事件 & HTML 标签属性速查手册,涵盖常用场景和示例,助你快速查阅和使用: 一、DOM 事件速查表 1. 鼠标事件 事件名触发时机适用元素示例代码click元素被点击任意可见元素button.addEventListener(click, () > { ... …...
差旅费控平台作用、功能、11款主流产品优劣势对比
本文将对比以下11款主流费控系统:合思、喜报销、泛微费控报销系统、经贝管家、每刻报销、SAP Concur、Expensify、Zoho Expense等。 随着全球化和企业跨地区运营的不断发展,企业差旅管理已逐渐成为许多公司面临的管理挑战之一。从差旅申请到费用报销的全…...
常用的配置文件格式对比(ini,toml,yaml,json,env,settings.py)及应用程序修改自身配置并保留注释
代码与环境配置解耦 git分支的代码应做到“环境无关”:代码本身不硬编码任何环境特定的配置(如数据库连接、密钥、API地址),而是通过外部机制动态注入。 配置与代码分离:将配置信息存储在代码库之外(如环…...
MySQL 存储过程详解
文章目录 1. 存储过程定义1.1 基本概念1.2 核心特点1.3 存储过程 vs 函数 2. 工作原理与示意图2.1 执行流程2.2 示意图 3. 使用场景3.1 复杂业务逻辑3.2 批量数据处理3.3 权限控制3.4 性能优化 4. 示例与说明4.1 基础示例:创建存储过程4.2 带输出参数的存储过程4.3 …...
使用DeepSeek/chatgpt等AI工具辅助网络协议流量数据包分析
随着deepseek,chatgpt等大模型的能力越来越强大,本文将介绍一下deepseek等LLM在分数流量数据包这方面的能力。为需要借助LLM等大模型辅助分析流量数据包的同学提供参考,也了解一下目前是否有必要继续学习wireshark工具以及复杂的协议知识。 pcap格式 目…...
源码压缩包泄露
##解题思路 因为网站的文件都放在www下面,所以直接访问/www.zip就可以得到网页的源码压缩包 在fl000g.txt这个文件中看到一个flag{flag_here}不像是真的flag,尝试提交ctfshow{flag_here},果然提交失败 打开文件属性之类的,也没有…...
TCP/IP 5层协议簇:数据链路层(交换机工作原理)
目录 1. 数据链路层 2. 帧 3. 工作在数据链路层的设备 4. 交换机工作原理 1. 数据链路层 数据链路层(2层Data Link Layer):传输单元是帧、这层工作的主要设备二层交换机、网卡 2. 帧 帧的结构如下:帧最大1518字节 如下&…...
TCP/IP的分层结构、各层的典型协议,以及与ISO七层模型的差别
1. TCP/IP的分层结构 TCP/IP模型是一个四层模型,主要用于网络通信的设计和实现。它的分层结构如下: (1) 应用层(Application Layer) 功能:提供应用程序之间的通信服务,处理特定的应用细节。 典型协议&am…...
【分布式理论11】分布式协同之分布式事务(一个应用操作多个资源):从刚性事务到柔性事务的演进
文章目录 一. 什么是分布式事务?二. 分布式事务的挑战三. 事务的ACID特性四. CAP理论与BASE理论1. CAP理论1.1. 三大特性1.2. 三者不能兼得 2. BASE理论 五. 分布式事务解决方案1. 两阶段提交(2PC)2. TCC(Try-Confirm-Cancel&…...
Linux: 已占用接口
Linux: 已占用接口 1. netstat(适用于旧系统)1.1 书中对该命令的介绍 2. ss(适用于新系统,替代 netstat)3. lsof(查看详细进程信息)4. fuser(快速查找占用端口的进程)5. …...
HWUI 和 Skia
📌 HWUI 和 Skia 的关系 Skia 是 Android 的底层 2D 图形库,提供 CPU 和 GPU 渲染能力,支持 OpenGL、Vulkan、Metal 等后端。HWUI 是 Android UI 组件的 GPU 渲染引擎,主要用于 加速 View、动画、阴影等 UI 元素的绘制。HWUI 依赖…...
Pytorch使用手册--将 PyTorch 模型导出为 ONNX(专题二十六)
注意 截至 PyTorch 2.1,ONNX 导出器有两个版本。 torch.onnx.dynamo_export 是最新的(仍处于测试阶段)导出器,基于 PyTorch 2.0 发布的 TorchDynamo 技术。 torch.onnx.export 基于 TorchScript 后端,自 PyTorch 1.2.0 起可用。 一、torch.onnx.dynamo_export使用 在 60 …...