一、内存构成
二、扩容
一般来说当内存空间span不足时,需要进行扩容。而在扩容前需要将当前没有剩余空间的内存块相关状态解除,以便后续的垃圾回收期能够进行扫描和回收,接着在从中间部件(central)提取新的内存块放回数组中。
// 根据空间规格 获取对应的索引 再获取alloc数组中对应的span
func (c *mcache) refill(spc spanClass){
s := c.alloc[spc]
if s != &emptymspan{ s.incache = false} // 解除当前span的状态
s = mheap_.central[spc].mcentral.cacheSpan // 从中间部件获取新的span
c.alloc[spc] = s //放回到数组
}
需要注意由于中间部件有scan和noscan两种类型,则申请的内存空间最终获取的可能是其两倍,并由heap堆进行统一管理。中间部件central是通过两个链表来管理其分配的所有内存块:
1、empty代表“无法使用”状态,没有剩余的空间或被移交给缓存的内存块
2、noempty代表剩余的空间,并这些内存块能够提供服务
有可能在垃圾回收后 还有些span内存空间没有被收回,但这并不影响其他线程来使用;由于每个内存块都有与垃圾清理相关的状态,当需要和垃圾回收期进行交互时,会关系到该如何复用每个内存块。
由于golang垃圾回收器使用的累增计数器(heap.sweepgen)来表达代龄的:
span.sweepgen =
sweepgen - 2 :代表当前内存块已被标记为垃圾 需要清理
sweepgen - 1 : 代表当前被标记为垃圾的内存块 正在清理
sweepgen : 代表为垃圾的内存块已完成清理,可再次使用
从上面内容可以看到每次进行清理操作时 该计数器 +2
再来看下mcentral的构成
type mcentral struct{
spanclass spanClass // span规格
nonempty mSpanList // 可使用的span
empty mSpanList // 不可使用的span
}
当通过mcentral进行空间span获取时,第一步需要到noempty列表检查剩余空间的内存块,这里面有一点需要说明主要是垃圾回收器的扫描过程和清理过程是同时进行的,那么为了获取更多的可用空间,则会在将分配的内存块移交给cache部件前,先完成清理的操作。第二步当noempty没有返回时,则需要检查下empty列表(由于empty里的内存块有可能已被标记为垃圾,这样可以直接清理,对应的空间则可直接使用了)。第三步若是noempty和empty都没有申请到,这时需要堆进行申请内存的
// 分配一块span便于在MCache中使用
func (c *mcentral) cacheSpan() *mspan {
retry:
var s *mspan
// 遍历noempty列表中有剩余空间的内存块
// 先执行清理工作 便于申请更多的空间
for s = c.nonempty.first; s != nil; s = s.next {
// 当前span的状态:进行垃圾清理
// 修改当前span的代龄状态
if s.sweepgen == sg-2 && atomic.Cas(&s.sweepgen, sg-2, sg-1) {
// 移除noempty列表中的记录
c.nonempty.remove(s)
// 添加empty列表的记录
c.empty.insertBack(s)
unlock(&c.lock)
s.sweep(true) // 清理
goto havespan
}
if s.sweepgen == sg-1 {
// 可能正被bgSweep或cacheSpan处理 则忽略该状态的span
continue
}
// 直接使用
c.nonempty.remove(s)
c.empty.insertBack(s)
unlock(&c.lock)
goto havespan
}
// 当noempty没有可用空间了 则需要从empty列表清理出可用空间
for s = c.empty.first; s != nil; s = s.next {
if s.sweepgen == sg-2 && atomic.Cas(&s.sweepgen, sg-2, sg-1) {
// 清理空间,并转移到empty列表尾部
c.empty.remove(s)
c.empty.insertBack(s)
unlock(&c.lock)
s.sweep(true)
// 查看是否有可用空间
freeIndex := s.nextFreeIndex()
if freeIndex != s.nelems {
s.freeindex = freeIndex
goto havespan
}
lock(&c.lock)
// 可能会出现清理后仍没有可用剩余空间
// 那就再重试noempty,继续前面的过程 直至找到可用的空间
goto retry
}
if s.sweepgen == sg-1 { // 正在被清理 跳过
continue
}
// already swept empty span,
// all subsequent ones must also be either swept or in process of sweeping
// 被移交给cache或清理后没有剩余空间的都会被追加到empty尾部
// 对应的sweepgen == sg(已清理)
// 遇到上述的内存块时, 表示后续无需再检查,直接跳出循环
break
}
// ......
// 从heap扩容 并直接返回.
s = c.grow()
if s == nil {
return nil
}
lock(&c.lock)
c.empty.insertBack(s)
unlock(&c.lock)
havespan:
// 更新span状态
s.incache = true
freeByteBase := s.freeindex &^ (64 - 1)
whichByte := freeByteBase / 8
// 分配位图
s.refillAllocCache(whichByte)
// 调整allocCache
// 使得span的freeindex处于allocCache的地位
s.allocCache >>= s.freeindex % 64
return s
}
通过上面的源码也可以看到中间部件central自身扩容操作与大对象内存分配差不多类似。
三、关于golang中微小对象(tiny)
在golang中将长度小于16bytes的对象称为微小对象(tiny),最常见的就是小字符串,一般会将这些微小对象组合起来,并用单块内存存储,这样能够有效的减少内存浪费。
当微小对象需要分配空间span,首先缓存部件会按指定的规格(tiny size class)取出一块内存,若容量不足,则重新提取一块;前面也提到会将微小对象进行组合,而这些组合的微小对象是不能包含指针的,因为垃圾回收的原因,一般都是当前存储单元里所有的微小对象都不可达时,才会将该块内存进行回收。
而当从缓冲部件cache中获取空间span时, 是通过偏移位置(tinyoffset)先来判断剩余空间是否满足需求。若是可以的话则以此计算并返回内存地址;若是空间不足,则提取新的内存块,直接返回起始地址便可; 最后在对比新旧两块内存,空间大的那块则会被保留。
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer{
if size <= maxSmallSize{
if noscan && size < maxTinySize{ // 是否使用微小对象分配器
off := c.tinyoffset // 当前内存块(cache.tiny)分配位置
// 剩余空间能够满足本次分配请求
if off + size <= maxTinySize && c.tiny != 0{
x = unsafe.Pointer(c.tiny + off) // 计算内存地址
c.tinyoffset = off + size // 调整下次分配位置
return x
}
// 剩余空间不足 则从cache中新取一块空间 根据空间规格spanclass申请
span := c.alloc[tinySpanClass]
v := nextFreeFast(span)
if v == 0{v,_,shouldhelpgc = c.nextFree(tinySpanClass)} //
// 直接获取起始地址
x = unsafe.Pointer(v)
(*[2]uint64)(x)[0] = 0
(*[2]uint64)(x)[1] = 0
// 对比新旧两块tiny内存 保留更大的那块
if size < c.tinyoffset || c.tiny == 0{
c.tiny = uintptr(x)
c.tinyoffset = size
}
}
}
}
在golang需要分配空间的object分三类:
1、零长度对象
2、小对象:需要先从缓存部件 接着中间部件 最后才是堆上申请
3、大对象:直接从堆heap上进行空间申请
相关推荐
在进行Map的赋值和扩容操作时,Golang会根据不同的情况选择不同的策略来优化性能。 1. **Map赋值操作**: 当我们对Map进行赋值操作,如`mapExample["hello"] = value`,Golang会调用相应的函数,如`mapassign`,...
weixin-golang-sdk 微信golang工具包
Golang 1.18.10 Windows安装包。Golang 1.18.10 Windows安装包。Golang 1.18.10 Windows安装包。Golang 1.18.10 Windows安装包。Golang 1.18.10 Windows安装包。Golang 1.18.10 Windows安装包。Golang 1.18.10 ...
golang_http_client
golang 实现海康相机抓拍,目前只支持球机。需要在代码中设置用户名和密码。 如何调试?用vscode打开,安装golang插件,即可直接调试运行。 编译与运行:运行go build命令,将HKnet.exe拷贝到build\Windows下,运行...
Golang Windows 1.19.1版本安装包
Golang,作为一种现代且高效的编程语言,因其强大的系统级编程能力和并发特性而受到广泛欢迎。在本文中,我们将深入探讨如何利用Golang实现这个功能,并了解相关的核心知识点。 首先,我们需要了解的是Golang的图形...
golang提取office文件内容,可以支持正常office文件内容格式,可以很好的提取标点以及内在格式内容
在Golang生态系统中,`golang.org/x`是一个非常重要的包集合,它包含了大量由Go官方维护和贡献的扩展库。这些包覆盖了各种功能,从网络编程到文本处理,为Go开发者提供了丰富的工具和解决方案。`golang.org/x`包系列...
Go语言,又称Golang,是由Google开发的一种静态类型、编译型、并发型且具有垃圾回收功能的编程语言。它设计的目标是提高开发者的生产效率,同时保持系统级编程的性能。Go语言的设计受到了C、 Pascal、 Miranda和...
golang函数查询手册.chm
在Golang中,如果你想要实现音频播放功能,特别是在处理MP3文件时,可以利用外部库,比如BASS库。BASS是一个广泛使用的音频处理库,它提供了强大的音频播放、流处理和格式转换功能。在Golang中使用BASS库通常需要...
Golang中文API,由热心网友翻译上传
从给定文件中提取的信息表明,我们这里讨论的是关于百度APP中Golang语言的应用实践。尽管提供的内容片段似乎是一些扫描的数字和符号,但我们可以从中抽取出一些关键点来构建Golang语言在百度APP开发中的应用知识。 ...
浅谈 Golang 并发 GC Golang 并发控制是指在 Golang 语言中,使用 Goroutine、Channel、WaitGroup 等机制来控制并发执行的过程。Golang 的并发控制机制可以确保多个 Goroutine 之间的数据安全和高效执行。 一、...
标题中的"Pycharm Golang插件 jar"指的是在PyCharm这款流行的Python集成开发环境中安装Golang编程语言的扩展插件。PyCharm是由JetBrains公司开发的一款强大的IDE,它支持多种编程语言,包括Python、Go等。由于Golang...
`golang.org/x` 是 Go 语言生态系统中的一个特殊仓库,包含了由 Go 语言官方维护但并未包含在标准库中的各种模块和工具。这个仓库包含了众多实用的第三方组件,如网络编程库、数据库驱动、文本处理工具等。由于 `...
golang 1.9.1 Windows安装包。
Golang精编100题涵盖了Golang编程语言的基础知识点和部分进阶内容,是Golang编程入门和进阶学习者的优质资源。本材料中包含了不同难度级别的题目,例如选择题、填空题、判断题和面试题,这些题目旨在帮助学习者从...