golang内存分配
golang内存分配
new一个对象的时候,入口函数是malloc.go中的newobject函数
func newobject(typ *_type) unsafe.Pointer {
flags := uint32(0)
if typ.kind&kindNoPointers != 0 {
flags |= flagNoScan
}
return mallocgc(uintptr(typ.size), typ, flags)
}
这个函数先计算出传入参数的大小,然后调用mallocgc函数,这个函数三个参数,第一个参数是对象类型大小,第二个参数是对象类型,第三个参数是malloc的标志位,这个标志位有两位,一个标志位代表GC不需要扫描这个对象,另一个标志位说明这个对象并不是空内存
const (
// flags to malloc
_FlagNoScan = 1 << 0 // GC doesn't have to scan object
_FlagNoZero = 1 << 1 // don't zero memory
)
mallocgc函数定义如下:
func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer
它返回的是指向这个结构的指针。
进入看里面的方法
先是会进行下面的操作
// 基本的条件符合判断 ...
// 获取当前goroutine的m结构
mp := acquirem()
// 如果当前的m正在执行分配任务,则抛出错误
if mp.mallocing != 0 {
throw("malloc deadlock")
}
if mp.gsignal == getg() {
throw("malloc during signal")
}
// 锁住当前的m进行分配
mp.mallocing = 1
shouldhelpgc := false
dataSize := size
// 获取当前goroutine的m的mcache
c := gomcache()
var s *mspan
var x unsafe.Pointer
其中的m,p,g的信息需要对下面这个图有印象
然后根据size判断是否是大对象,小对象,微小对象
如果是微小对象:
// 是微小对象
// 进行微小对象的校准操作
// ...
// 如果是微小对象,并且申请的对象微小对象能cover住
if off+size <= maxTinySize && c.tiny != nil {
// 直接在tiny的块中进行分配就行了
x = add(c.tiny, off)
...
return x
}
// 从mcache中获取对应的span链表
s = c.alloc[tinySizeClass]
v := s.freelist
// 如果这个span链表没有微小对象的空闲span了,从MCache中获取tinySize的链表补充上这个tiny链表
if v.ptr() == nil {
systemstack(func() {
mCache_Refill(c, tinySizeClass)
})
}
s.freelist = v.ptr().next
s.ref++
// 预读取指令能加快速度
prefetchnta(uintptr(v.ptr().next))
// 初始化微小结构
x = unsafe.Pointer(v)
(*[2]uint64)(x)[0] = 0
(*[2]uint64)(x)[1] = 0
// 对比新旧两个tiny块剩余空间
if size < c.tinyoffset {
// 如果旧块的剩余空间比新块少,则使用新块替代mcache中的tiny块
c.tiny = x
c.tinyoffset = size
}
如果是小对象
// 是小对象
var sizeclass int8
// 计算最接近的size
if size <= 1024-8 {
sizeclass = size_to_class8[(size+7)>>3]
} else {
sizeclass = size_to_class128[(size-1024+127)>>7]
}
size = uintptr(class_to_size[sizeclass])
// 获取mcache中预先分配的spans链表
s = c.alloc[sizeclass]
v := s.freelist
if v.ptr() == nil {
// 如果没有链表了,则从mcache中划出对应的spans链表
systemstack(func() {
mCache_Refill(c, int32(sizeclass))
})
}
// 有链表则直接使用
s.freelist = v.ptr().next
s.ref++
如果是大对象,则直接从heap上拿内存
// 如果是大对象,直接去heap中获取数据
systemstack(func() {
s = largeAlloc(size, uint32(flags))
})
x = unsafe.Pointer(uintptr(s.start << pageShift))
size = uintptr(s.elemsize)
总结一下
- 如果要申请的对象是tiny大小,看mcache中的tiny block是否足够,如果足够,直接分配。如果不足够,使用mcache中的tiny class对应的span分配
- 如果要申请的对象是小对象大小,则使用mcache中的对应span链表分配
- 如果对应span链表已经没有空span了,先补充上mcache的对应链表,再分配(mCache_Refill)
- 如果要申请的对象是大对象,直接去heap中获取(largeAlloc)
再仔细看代码,不管是tiny大小的对象还是小对象,他们去mcache中获取对象都是使用mCache_Refill方法为这个对象对应的链表申请内存。那么我们可以追到里面去看看。
func mCache_Refill(c *mcache, sizeclass int32) *mspan {
// 获取当时的goroutine
_g_ := getg()
// 锁上m
_g_.m.locks++
// 获取对应sizeclass的span链表,如果对应的链表还有剩余空间,抛出错误
s := c.alloc[sizeclass]
if s.freelist.ptr() != nil {
throw("refill on a nonempty span")
}
// 从mCentral中获取span链表,并赋值
s = mCentral_CacheSpan(&mheap_.central[sizeclass].mcentral)
c.alloc[sizeclass] = s
// 打开锁
_g_.m.locks--
return s
}
这里实际是使用mCentral_CacheSpan来获取内存,这里需要看下mCentral的结构
type mcentral struct {
lock mutex
sizeclass int32
nonempty mspan // list of spans with a free object
empty mspan // list of spans with no free objects (or cached in an mcache)
}
mcentral有两个链表,一个链表是有空闲的span可以使用,叫noempty,另一个链表是没有空间的span可以使用,叫empty。这个时候我们需要获取span,一定是从nonempty链表中取出span来使用。
这两个链表的机制是这样的,我new一个对象的时候,从nonempty中获取这个空间,放到empty链表中去,当我free一个对象的时候,从empty链表中还原到nonempty链表中去。
所以在下面获取空span的时候,会先去empty中查找有没有,如果没有,再去nonempty中查找有没有,nonempty中有可能有为资源回收但是却是没有使用的span。
func mCentral_CacheSpan(c *mcentral) *mspan {
sg := mheap_.sweepgen
retry:
var s *mspan
// 遍历有空间span的链表
for s = c.nonempty.next; s != &c.nonempty; s = s.next {
// 如果这个span是需要回收的,那么先回收这个span,转移到empty链表中,再把这个span返回
if s.sweepgen == sg-2 && cas(&s.sweepgen, sg-2, sg-1) {
mSpanList_Remove(s)
mSpanList_InsertBack(&c.empty, s)
unlock(&c.lock)
// 垃圾清理
mSpan_Sweep(s, true)
goto havespan
}
// 如果nonempty中有不需要swapping的空间,这个就可以直接使用了
mSpanList_Remove(s)
mSpanList_InsertBack(&c.empty, s)
unlock(&c.lock)
goto havespan
}
// 遍历没有空间的span链表,为什么没有空间的span链表也需要遍历呢?
for s = c.empty.next; s != &c.empty; s = s.next {
// 如果这个span是需要回收的,回收之
if s.sweepgen == sg-2 && cas(&s.sweepgen, sg-2, sg-1) {
mSpanList_Remove(s)
mSpanList_InsertBack(&c.empty, s)
unlock(&c.lock)
mSpan_Sweep(s, true)
if s.freelist.ptr() != nil {
goto havespan
}
lock(&c.lock)
goto retry
}
break
}
unlock(&c.lock)
// 到这里就说明central中都没有可以使用的span了,那么,就增长mCentral
s = mCentral_Grow(c)
mSpanList_InsertBack(&c.empty, s)
havespan:
// 找到空span的情况
cap := int32((s.npages << _PageShift) / s.elemsize)
n := cap - int32(s.ref)
if n == 0 {
throw("empty span")
}
if s.freelist.ptr() == nil {
throw("freelist empty")
}
s.incache = true
return s
}
mCentral判断一个span是否过期是使用
s.sweepgen == sg-2 && cas(&s.sweepgen, sg-2, sg-1)
这个sweepgen是span和mheap中各有一个,根据这两个结构的sweepgen就能判断这个span是否需要进入gc回收了。
// sweep generation:
// if sweepgen == h->sweepgen - 2, the span needs sweeping
// if sweepgen == h->sweepgen - 1, the span is currently being swept
// if sweepgen == h->sweepgen, the span is swept and ready to use
// h->sweepgen is incremented by 2 after every GC
如果mCentral没有可用的span了,就需要调用mCentral_Grow(c)
func mCentral_Grow(c *mcentral) *mspan {
...
// 从heap上进行分配
s := mHeap_Alloc(&mheap_, npages, c.sizeclass, false, true)
...
// 设置span的bitmap
heapBitsForSpan(s.base()).initSpan(s.layout())
return s
}
再进入到mHeap_Alloc
func mHeap_Alloc(h *mheap, npage uintptr, sizeclass int32, large bool, needzero bool) *mspan {
...
systemstack(func() {
s = mHeap_Alloc_m(h, npage, sizeclass, large)
})
...
}
再进入mHeap_Alloc_m
func mHeap_Alloc_m(h *mheap, npage uintptr, sizeclass int32, large bool) *mspan {
...
s := mHeap_AllocSpanLocked(h, npage)
...
return s
}
func mHeap_AllocSpanLocked(h *mheap, npage uintptr) *mspan {
...
// 获取Heap中最合适的内存大小
s = mHeap_AllocLarge(h, npage)
// 如果mHeap满了
if s == nil {
// 增长mHeap大小
if !mHeap_Grow(h, npage) {
return nil
}
s = mHeap_AllocLarge(h, npage)
if s == nil {
return nil
}
}
HaveSpan:
// mHeap中有了数据
}
看看如何增长mHeap大小
func mHeap_Grow(h *mheap, npage uintptr) bool {
...
// 调用操作系统分配内存
v := mHeap_SysAlloc(h, ask)
...
}
下面就看到mheap的扩容了,这个之前需要了解heap的结构
type mheap struct {
lock mutex
free [_MaxMHeapList]mspan // free lists of given length
freelarge mspan // free lists length >= _MaxMHeapList
busy [_MaxMHeapList]mspan // busy lists of large objects of given length
busylarge mspan // busy lists of large objects length >= _MaxMHeapList
allspans **mspan // all spans out there
gcspans **mspan // copy of allspans referenced by gc marker or sweeper
nspan uint32
sweepgen uint32 // sweep generation, see comment in mspan
sweepdone uint32 // all spans are swept
// span lookup
spans **mspan
spans_mapped uintptr
// Proportional sweep
spanBytesAlloc uint64 // bytes of spans allocated this cycle; updated atomically
pagesSwept uint64 // pages swept this cycle; updated atomically
sweepPagesPerByte float64 // proportional sweep ratio; written with lock, read without
// Malloc stats.
largefree uint64 // bytes freed for large objects (>maxsmallsize)
nlargefree uint64 // number of frees for large objects (>maxsmallsize)
nsmallfree [_NumSizeClasses]uint64 // number of frees for small objects (<=maxsmallsize)
// range of addresses we might see in the heap
bitmap uintptr
bitmap_mapped uintptr
arena_start uintptr
arena_used uintptr // always mHeap_Map{Bits,Spans} before updating
arena_end uintptr
arena_reserved bool
// central free lists for small size classes.
// the padding makes sure that the MCentrals are
// spaced CacheLineSize bytes apart, so that each MCentral.lock
// gets its own cache line.
central [_NumSizeClasses]struct {
mcentral mcentral
pad [_CacheLineSize]byte
}
spanalloc fixalloc // allocator for span*
cachealloc fixalloc // allocator for mcache*
specialfinalizeralloc fixalloc // allocator for specialfinalizer*
specialprofilealloc fixalloc // allocator for specialprofile*
speciallock mutex // lock for special record allocators.
}
它最重要的结构有三个,spans,指向所有span指针,bitmap是spans的标志位,arena是堆生成区。
+---------------------+---------------+-----------------------------+
| spans 512MB .......| bitmap 32GB | arena 512GB ..................|
+---------------------+---------------+-----------------------------+ +
func mHeap_SysAlloc(h *mheap, n uintptr) unsafe.Pointer {
// 如果超出了arean预留的区块限制了
if n > uintptr(h.arena_end)-uintptr(h.arena_used) {
// 使用一些系统保留的空间
...
}
// 申请的大小在arean范围内
if n <= uintptr(h.arena_end)-uintptr(h.arena_used) {
// 使用系统的sysMap申请内存
sysMap((unsafe.Pointer)(p), n, h.arena_reserved, &memstats.heap_sys)
mHeap_MapBits(h, p+n)
mHeap_MapSpans(h, p+n)
...
}
...
}
func sysMap(v unsafe.Pointer, n uintptr, reserved bool, sysStat *uint64) {
...
// 最终调用mmap
p := mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0)
...
}
参考文章
golang内存分配的更多相关文章
- 图解golang内存分配机制 (转)
一般程序的内存分配 在讲Golang的内存分配之前,让我们先来看看一般程序的内存分布情况: 以上是程序内存的逻辑分类情况. 我们再来看看一般程序的内存的真实(真实逻辑)图: Go的内存分配核心思想 G ...
- Golang内存分配内置函数之new函数
new函数用来分配内存,主要分配值类型,比如int.float32.struct等,返回的是指针 package main import ( "fmt" ) func main() ...
- 深入理解golang:内存分配原理
一.Linux系统内存 在说明golang内存分配之前,先了解下Linux系统内存相关的基础知识,有助于理解golang内存分配原理. 1.1 虚拟内存技术 在早期内存管理中,如果程序太大,超过了空闲 ...
- Go语言内存管理(一)内存分配
Go语言内存管理(一)内存分配 golang作为一种"高级语言",也提供了自己的内存管理机制.这样一方面可以简化编码的流程,降低因内存使用导致出现问题的频率(C语言使用者尤其是初学 ...
- TCMalloc 内存分配原理简析
一.TCMalloc TCMalloc简介 为啥要介绍 TCMalloc? 因为golang的内存分配算法绝大部分都是来自 TCMalloc,golang只改动了其中的一小部分.所以要理解golang ...
- golang 学习笔记 ---内存分配与管理
Go语言——内存管理 参考: 图解 TCMalloc Golang 内存管理 Go 内存管理 问题 内存碎片:避免内存碎片,提高内存利用率. 多线程:稳定性,效率问题. 内存分配 内存划分 are ...
- c#程序内存分配
c#程序内存分配 进程可使用内存数就是操作系统给进程分配的最大地址,一般的32位操作系统提供给用户地址最大都是3g(操作系统自己保留1g),windows由于商业目的,对于个人用户只提供了2g地址,要 ...
- 图解Go语言内存分配
目录 基础概念 内存管理单元 内存管理组件 mcache mcentral mheap 内存分配流程 总结 参考资料 Go语言内置运行时(就是runtime),抛弃了传统的内存分配方式,改为自主管理. ...
- Golang内存管理
Golang 内存管理 原文链接[http://legendtkl.com/2017/04/02/golang-alloc/] Golang 的内存管理基于 tcmalloc,可以说起点挺高的.但是 ...
随机推荐
- android 退出机制
android sdk 退出机制的研究 有多种, 方法一.用list保存activity实例,然后逐一干掉 上代码: import java.util.LinkedList; import java. ...
- linux下用rpm包安装默认配置
rpm安装默认目录:数据文件:/var/lib/mysql/配置文件模板:/usr/share/mysqlmysql客户端工具目录:/usr/bin日志目录:/var/log/pid,sock文件目录 ...
- node(md5)
md5是一种信息-摘要算法,即针对一段明文给出一个hash值,在密码学中最经典的用法是验证数据的完整性,因为一旦原始数据发生改变那么生成的摘要也必将不同. 网络中md5可以用于用户密码的加密,即在数据 ...
- 必应词典UWP版-开发小结
摘要 必应词典UWP版已经上线2周了!相信有不少用户都已经体验过了吧!得益于Win10全新.强大的API,新版词典在性能上.UI体验上都有了大幅的提升,今天,小编就为大家讲讲必应词典UWP开发的故事. ...
- 一天一小段js代码(no.2)
(一)可以用下面js代码来检测弹出窗口是否被屏蔽: var blocked = false ; try { /*window.open()方法接受4个参数window.open(要加载的url,窗口目 ...
- 翻译-使用Spring调用SOAP Web Service
原文链接: http://spring.io/guides/gs/consuming-web-service/ 调用SOAP web service 本指南将指导你使用Spring调用一个基于SOAP ...
- HTML5触屏版多线程渲染模板技术分享
前言: 了解js编译原理的屌丝们都知道,js是单线程的,想当年各路神仙为了实现js的多线程,为了解决innerHTML输出大段HTML卡页面的顽疾,纷纷设计了诸如假冒的“多线程“实现,我自己也在写开源 ...
- spring源码分析(二)Aop
创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...
- Atitit 基于dom的游戏引擎
Atitit 基于dom的游戏引擎 1. 添加sprite控件(cocos,createjs,dom)1 1.1.1. Cocos1 1.1.2. createjs1 1.1.3. Dom模式2 1. ...
- Atitti.数字证书体系cer pfx attilax总结
Atitti.数字证书体系cer pfx attilax总结 一.数字证书常见标准 1 数字证书文件格式(cer和pfx)的区别: 1 二.数字证书存储内容 2 X.509是一种非常通用的证书格式. ...