Go内存管理一文足矣
进程的内存空间
- 程序文件段(.text),包括二进制可执行代码;
- 已初始化数据段(.data),包括静态常量;
- 未初始化数据段(.bss),包括未初始化的静态变量;(bss与data一般作为静态存储区)
- 堆段,包括动态分配的内存,从低地址开始向上增长;
- 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关 (opens new window));
- 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小;
堆内存分配
一个简单的内存分配器
- sbrk(0)获取当前brk的地址
- 调用sbrk(x),x为正数时,请求分配x bytes的内存空间,x为负数时,请求释放x bytes的内存空间
void *malloc(size_t size) {
void *block;
block = sbrk(size);
if (block == (void *) -1) {
return NULL;
}
return block;
}
typedef char ALIGN[16]; // padding字节对齐使用 union header {
struct {
size_t size; // 块大小
unsigned is_free; // 是否有在使用
union header *next; // 下一个块的地址
} s;
ALIGN stub;
};
typedef union header header_t;
header_t *head, *tail
pthread_mutex_t global_malloc_lock;
void *malloc(size_t size)
{
size_t total_size;
void *block;
header_t *header;
if (!size) // 如果size为0或者NULL直接返回null
return NULL;
pthread_mutex_lock(&global_malloc_lock); // 全局加锁
header = get_free_block(size); // 先从已空闲区域找一块合适大小的内存
if (header) { // 如果能找到就直接使用,无需每次向操作系统申请
header->s.is_free = 0; // 标志这块区域非空闲
pthread_mutex_unlock(&global_malloc_lock); // 解锁
// 这个header对外部应该是完全隐藏的,真正用户需要的内存在header尾部的下一个位置
return (void*)(header + 1);
}
// 如果空闲区域没有则向操作系统申请一块内存,因为我们需要header存储一些元数据
// 所以这里要申请的内存实际是元数据区+用户实际需要的大小
total_size = sizeof(header_t) + size;
block = sbrk(total_size);
if (block == (void*) -1) { // 获取失败解锁、返回NULL
pthread_mutex_unlock(&global_malloc_lock);
return NULL;
}
// 申请成功设置元数据信息
header = block;
header->s.size = size;
header->s.is_free = 0;
header->s.next = NULL;
// 更新链表对应指针
if (!head)
head = header;
if (tail)
tail->s.next = header;
tail = header;
// 解锁返回给用户内存
pthread_mutex_unlock(&global_malloc_lock);
return (void*)(header + 1);
} // 这个函数从链表中已有的内存块进行判断是否存在空闲的,并且能够容得下申请区域的内存块
// 有则返回,每次都从头遍历,暂不考虑性能和内存碎片问题。
header_t *get_free_block(size_t size)
{
header_t *curr = head;
while(curr) {
if (curr->s.is_free && curr->s.size >= size)
return curr;
curr = curr->s.next;
}
return NULL;
}
- 通过加锁保证线程安全
- 通过链表的方式管理内存块,并解决内存复用问题。
void free(void *block)
{
header_t *header, *tmp;
void *programbreak; if (!block)
return;
pthread_mutex_lock(&global_malloc_lock); // 全局加锁
header = (header_t*)block - 1; // block转变为header_t为单位的结构,并向前移动一个单位,也就是拿到了这个块的元数据的起始地址 programbreak = sbrk(0); // 获取当前brk指针的位置
if ((char*)block + header->s.size == programbreak) { // 如果当前内存块的末尾位置(即tail块)刚好是brk指针位置就把它还给操作系统
if (head == tail) { // 只有一个块,直接将链表设置为空
head = tail = NULL;
} else {// 存在多个块,则找到tail的前一个快,并把它next设置为NULL
tmp = head;
while (tmp) {
if(tmp->s.next == tail) {
tmp->s.next = NULL;
tail = tmp;
}
tmp = tmp->s.next;
}
}
// 将内存还给操作系统
sbrk(0 - sizeof(header_t) - header->s.size);
pthread_mutex_unlock(&global_malloc_lock); // 解锁
return;
}
// 如果不是最后的链表就标志位free,后面可以复用
header->s.is_free = 1;
pthread_mutex_unlock(&global_malloc_lock);
}
- 全局锁在高并发场景下会带来严重性能问题
- 内存复用每次从头遍历也存在一些性能问题
- 内存碎片问题,我们内存复用时只是简单的判断块内存是否大于需要的内存区域,如果极端情况下,我们一块空闲内存为1G,而新申请内存为1kb,那就造成严重的碎片浪费
- 内存释放存在问题,只会把末尾处的内存还给操作系统,中间的空闲部分则没有机会还给操作系统。
TCMalloc
- Page:操作系统对内存管理以页为单位,TCMalloc也是这样,只不过TCMalloc里的Page大小与操作系统里的大小并不一定相等,而是倍数关系。《TCMalloc解密》里称x64下Page大小是8KB。
- Span:一组连续的Page被称为Span,比如可以有2个页大小的Span,也可以有16页大小的Span,Span比Page高一个层级,是为了方便管理一定大小的内存区域,Span是TCMalloc中内存管理的基本单位。
- ThreadCache:每个线程各自的Cache,一个Cache包含多个空闲内存块链表,每个链表连接的都是内存块,同一个链表上内存块的大小是相同的,也可以说按内存块大小,给内存块分了个类,这样可以根据申请的内存大小,快速从合适的链表选择空闲内存块。由于每个线程有自己的ThreadCache,所以ThreadCache访问是无锁的。
- CentralCache:是所有线程共享的缓存,也是保存的空闲内存块链表,链表的数量与ThreadCache中链表数量相同,当ThreadCache内存块不足时,可以从CentralCache取,当ThreadCache内存块多时,可以放回CentralCache。由于CentralCache是共享的,所以它的访问是要加锁的。
- PageHeap:PageHeap是堆内存的抽象,PageHeap存的也是若干链表,链表保存的是Span,当CentralCache没有内存的时,会从PageHeap取,把1个Span拆成若干内存块,添加到对应大小的链表中,当CentralCache内存多的时候,会放回PageHeap。如上图,分别是1页Page的Span链表,2页Page的Span链表等,最后是large span set,这个是用来保存中大对象的。毫无疑问,PageHeap也是要加锁的。
- 小对象大小:0~256KB;分配流程:ThreadCache -> CentralCache -> HeapPage,大部分时候,ThreadCache缓存都是足够的,不需要去访问CentralCache和HeapPage,无锁分配加无系统调用,分配效率是非常高的。
- 中对象大小:257~1MB;分配流程:直接在PageHeap中选择适当的大小即可,128 Page的Span所保存的最大内存就是1MB。
- 大对象大小:>1MB;分配流程:从large span set选择合适数量的页面组成span,用来存储数据。
Go内存分配方案
- 栈比堆更高效,不需要GC,因此Go会尽可能的将内存分配到栈上。Go的协程栈可以自动扩容和缩容
- 当分配到栈上可能会引起非法内存访问等问题,则会使用堆,如:
- 当一个值在函数被调用后访问(即作为返回值返回变量地址),这个值极有可能被分配到堆上
- 当编译器检测到某个值过大,这个值被分配到堆上(栈扩容和缩容有成本)
- 当编译时,编译器不知道这个值的大小(slice、map等引用类型)这个值会被分配到堆上
- 最后,不要去猜值在哪,只有编译器和编译器开发者知道
- 微小对象(0,16byte):分配流程为,mache->mcentral->mheap位图查找->mheap基数树查找->操作系统分配
- 小对象 [16byte, 32KB]:分配流程与微小对象一样
- 大对象(32KB以上):分为流程为,mheap基数树查找->操作系统分配(不经过mcache和mcentral)
span
// path: /usr/local/go/src/runtime/sizeclasses.go
const _NumSizeClasses = 67
var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536,1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}
// path: /usr/local/go/src/runtime/sizeclasses.go const _NumSizeClasses = 67 var class_to_allocnpages = [_NumSizeClasses]uint8{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 2, 3, 1, 3, 2, 3, 4, 5, 6, 1, 7, 6, 5, 4, 3, 5, 7, 2, 9, 7, 5, 8, 3, 10, 7, 4}
// path: /usr/local/go/src/runtime/mheap.go type mspan struct {
//链表前向指针,用于将span链接起来
next *mspan //链表前向指针,用于将span链接起来
prev *mspan // 起始地址,也即所管理页的地址
startAddr uintptr // 管理的页数
npages uintptr
// 块个数,表示有多少个块可供分配
nelems uintptr
// 用来辅助确定当前span中的元素分配到了哪里
freeindex uintptr //分配位图,每一位代表一个块是否已分配
allocBits *gcBits
// allocBits的补码,以用来快速查找内存中未被使用的内存
allocCache unit64 // 已分配块的个数
allocCount uint16 // class表中的class ID,和Size Classs相关
spanclass spanClass // class表中的对象大小,也即块大小
elemsize uintptr
// GC中来标记哪些块已经释放了
gcmarkBits *gcBits
}
mcache
//path: /usr/local/go/src/runtime/mcache.go type mcache struct {
// mcache中对应各个等级的span都会有两份缓存
alloc [numSpanClasses]*mspan
// 下面三个是在微小对象分配时专门使用
tiny uintptr
tinyoffset uintptr
local_tinyallocs uintptr
} numSpanClasses = _NumSizeClasses << 1
// init initializes pp, which may be a freshly allocated p or a
// previously destroyed p, and transitions it to status _Pgcstop.
func (pp *p) init(id int32) {
pp.id = id
////////
.........
/////////
if pp.mcache == nil {
if id == 0 {
if mcache0 == nil {
throw("missing mcache?")
}
// Use the bootstrap mcache0. Only one P will get
// mcache0: the one with ID 0.
pp.mcache = mcache0
} else {
pp.mcache = allocmcache()
}
}
..........
}
// dummy mspan that contains no free objects.
var emptymspan mspan
func allocmcache() *mcache {
var c *mcache
// 在系统栈中调用mheap的缓存分配器创建mcache
systemstack(func() {
lock(&mheap_.lock) // mheap是所有协程共用的需要加锁访问
c = (*mcache)(mheap_.cachealloc.alloc())
c.flushGen = mheap_.sweepgen
unlock(&mheap_.lock)
})
// 将alloc数组设置为空span
for i := range c.alloc {
c.alloc[i] = &emptymspan
}
c.nextSample = nextSample()
return c
}
// refill acquires a new span of span class spc for c. This span will
// have at least one free object. The current span in c must be full.
//
// Must run in a non-preemptible context since otherwise the owner of
// c could change.
func (c *mcache) refill(spc spanClass) {
// Return the current cached span to the central lists.
s := c.alloc[spc]
...............
if s != &emptymspan {
// Mark this span as no longer cached.
if s.sweepgen != mheap_.sweepgen+3 {
throw("bad sweepgen in refill")
}
mheap_.central[spc].mcentral.uncacheSpan(s)
} // Get a new cached span from the central lists.
s = mheap_.central[spc].mcentral.cacheSpan()
................
...............
c.alloc[spc] = s
}
mcentral
type mcentral struct {
spanclass spanClass
partial [2]spanSet // list of spans with a free object
full [2]spanSet // list of spans with no free objects
}
type spanSet struct {
spineLock mutex
spine unsafe.Pointer // *[N]*spanSetBlock, accessed atomically
spineLen uintptr // Spine array length, accessed atomically
spineCap uintptr // Spine array cap, accessed under lock index headTailIndex
}
off := c.tinyoffset
// Align tiny pointer for required (conservative) alignment.
if size&7 == 0 {
off = alignUp(off, 8)
} else if sys.PtrSize == 4 && size == 12 {
// Conservatively align 12-byte objects to 8 bytes on 32-bit
// systems so that objects whose first field is a 64-bit
// value is aligned to 8 bytes and does not cause a fault on
// atomic access. See issue 37262.
// TODO(mknyszek): Remove this workaround if/when issue 36606
// is resolved.
off = alignUp(off, 8)
} else if size&3 == 0 {
off = alignUp(off, 4)
} else if size&1 == 0 {
off = alignUp(off, 2)
}
if off+size <= maxTinySize && c.tiny != 0 {
// The object fits into existing tiny block.
x = unsafe.Pointer(c.tiny + off)
c.tinyoffset = off + size
c.tinyAllocs++
mp.mallocing = 0
releasem(mp)
return x
}
// Allocate a new maxTinySize block.
span = c.alloc[tinySpanClass]
v := nextFreeFast(span)
if v == 0 {
v, span, shouldhelpgc = c.nextFree(tinySpanClass)
}
x = unsafe.Pointer(v)
(*[2]uint64)(x)[0] = 0
(*[2]uint64)(x)[1] = 0
// See if we need to replace the existing tiny block with the new one
// based on amount of remaining free space.
if !raceenabled && (size < c.tinyoffset || c.tiny == 0) {
// Note: disabled when race detector is on, see comment near end of this function.
c.tiny = uintptr(x)
c.tinyoffset = size
}
size = maxTinySize
var sizeclass uint8
if size <= smallSizeMax-8 {
sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]
} else {
sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]
}
size = uintptr(class_to_size[sizeclass])
spc := makeSpanClass(sizeclass, noscan)
span = c.alloc[spc]
v := nextFreeFast(span)
if v == 0 {
v, span, shouldhelpgc = c.nextFree(spc)
}
x = unsafe.Pointer(v)
if needzero && span.needzero != 0 {
memclrNoHeapPointers(unsafe.Pointer(v), size)
}
// nextFreeFast returns the next free object if one is quickly available.
// Otherwise it returns 0.
func nextFreeFast(s *mspan) gclinkptr {
theBit := sys.Ctz64(s.allocCache) // Is there a free object in the allocCache?
if theBit < 64 {
result := s.freeindex + uintptr(theBit)
if result < s.nelems {
freeidx := result + 1
if freeidx%64 == 0 && freeidx != s.nelems {
return 0
}
s.allocCache >>= uint(theBit + 1)
s.freeindex = freeidx
s.allocCount++
return gclinkptr(result*s.elemsize + s.base())
}
}
return 0
}
// nextFreeIndex returns the index of the next free object in s at
// or after s.freeindex.
// There are hardware instructions that can be used to make this
// faster if profiling warrants it.
func (s *mspan) nextFreeIndex() uintptr {
sfreeindex := s.freeindex
snelems := s.nelems
if sfreeindex == snelems {
return sfreeindex
}
if sfreeindex > snelems {
throw("s.freeindex > s.nelems")
} aCache := s.allocCache bitIndex := sys.Ctz64(aCache)
for bitIndex == 64 {
// Move index to start of next cached bits.
sfreeindex = (sfreeindex + 64) &^ (64 - 1)
if sfreeindex >= snelems {
s.freeindex = snelems
return snelems
}
whichByte := sfreeindex / 8
// Refill s.allocCache with the next 64 alloc bits.
s.refillAllocCache(whichByte)
aCache = s.allocCache
bitIndex = sys.Ctz64(aCache)
// nothing available in cached bits
// grab the next 8 bytes and try again.
}
result := sfreeindex + uintptr(bitIndex)
if result >= snelems {
s.freeindex = snelems
return snelems
} s.allocCache >>= uint(bitIndex + 1)
sfreeindex = result + 1 if sfreeindex%64 == 0 && sfreeindex != snelems {
// We just incremented s.freeindex so it isn't 0.
// As each 1 in s.allocCache was encountered and used for allocation
// it was shifted away. At this point s.allocCache contains all 0s.
// Refill s.allocCache so that it corresponds
// to the bits at s.allocBits starting at s.freeindex.
whichByte := sfreeindex / 8
s.refillAllocCache(whichByte)
}
s.freeindex = sfreeindex
return result
}
func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) {
s = c.alloc[spc]
shouldhelpgc = false
freeIndex := s.nextFreeIndex() // 获取可分配的元素位置
if freeIndex == s.nelems {
//如果当前span没有可分配空间,调用refill方法把当前span交给mcentral的full队列
// 并从mcentral的partial队列取一个有空闲的span放到mcache上
// The span is full.
if uintptr(s.allocCount) != s.nelems {
println("runtime: s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
throw("s.allocCount != s.nelems && freeIndex == s.nelems")
}
c.refill(spc)
shouldhelpgc = true
s = c.alloc[spc] freeIndex = s.nextFreeIndex() // 在新获取的span中重新计算freeIndex
} if freeIndex >= s.nelems {
throw("freeIndex is not valid")
} v = gclinkptr(freeIndex*s.elemsize + s.base()) // 获取span中数据的起始地址加上当前已分配的区域获取一个可分配的空闲区域
s.allocCount++
if uintptr(s.allocCount) > s.nelems {
println("s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
throw("s.allocCount > s.nelems")
}
return
}
func (c *mcache) refill(spc spanClass) {
// Return the current cached span to the central lists.
s := c.alloc[spc]
...............
if s != &emptymspan {
// Mark this span as no longer cached.
if s.sweepgen != mheap_.sweepgen+3 {
throw("bad sweepgen in refill")
}
mheap_.central[spc].mcentral.uncacheSpan(s)
} // Get a new cached span from the central lists.
s = mheap_.central[spc].mcentral.cacheSpan()
................
...............
c.alloc[spc] = s
}
- 是先从partail队列中已经被垃圾回收清扫的部分尝试拿一个span
- 如果pop没有代表当前没有被GC清扫的span,从partial队列中未被GC清扫的部分尝试获取空闲span,并进行清扫
- 如果partail队列都没获取到,尝试从full队列的未清扫区获取一个span,进行清扫,并放入到full队列的以清扫区,代表这个span不会分配给其他的mcache了;
- 如果未清扫区也没有获取到对应的span则代表mcentral需要扩容,向mheap申请一块区域。
// Allocate a span to use in an mcache.
func (c *mcentral) cacheSpan() *mspan {
// Deduct credit for this span allocation and sweep if necessary.
spanBytes := uintptr(class_to_allocnpages[c.spanclass.sizeclass()]) * _PageSize
deductSweepCredit(spanBytes, 0) traceDone := false
if trace.enabled {
traceGCSweepStart()
} spanBudget := 100 var s *mspan
sl := newSweepLocker()
sg := sl.sweepGen // Try partial swept spans first.
if s = c.partialSwept(sg).pop(); s != nil {
goto havespan
} // Now try partial unswept spans.
for ; spanBudget >= 0; spanBudget-- {
s = c.partialUnswept(sg).pop()
if s == nil {
break
}
if s, ok := sl.tryAcquire(s); ok {
// We got ownership of the span, so let's sweep it and use it.
s.sweep(true)
sl.dispose()
goto havespan
}
}
// Now try full unswept spans, sweeping them and putting them into the
// right list if we fail to get a span.
for ; spanBudget >= 0; spanBudget-- {
s = c.fullUnswept(sg).pop()
if s == nil {
break
}
if s, ok := sl.tryAcquire(s); ok {
// We got ownership of the span, so let's sweep it.
s.sweep(true)
// Check if there's any free space.
freeIndex := s.nextFreeIndex()
if freeIndex != s.nelems {
s.freeindex = freeIndex
sl.dispose()
goto havespan
}
// Add it to the swept list, because sweeping didn't give us any free space.
c.fullSwept(sg).push(s.mspan)
}
// See comment for partial unswept spans.
}
sl.dispose()
if trace.enabled {
traceGCSweepDone()
traceDone = true
} // We failed to get a span from the mcentral so get one from mheap.
s = c.grow()
if s == nil {
return nil
} // At this point s is a span that should have free slots.
havespan:
if trace.enabled && !traceDone {
traceGCSweepDone()
}
n := int(s.nelems) - int(s.allocCount)
if n == 0 || s.freeindex == s.nelems || uintptr(s.allocCount) == s.nelems {
throw("span has no free objects")
}
freeByteBase := s.freeindex &^ (64 - 1)
whichByte := freeByteBase / 8
// Init alloc bits cache.
s.refillAllocCache(whichByte) // Adjust the allocCache so that s.freeindex corresponds to the low bit in
// s.allocCache.
s.allocCache >>= s.freeindex % 64 return s
}
func (c *mcentral) uncacheSpan(s *mspan) {
if s.allocCount == 0 {
throw("uncaching span but s.allocCount == 0")
} sg := mheap_.sweepgen
stale := s.sweepgen == sg+1 // Fix up sweepgen.
if stale {
// Span was cached before sweep began. It's our
// responsibility to sweep it.
//
// Set sweepgen to indicate it's not cached but needs
// sweeping and can't be allocated from. sweep will
// set s.sweepgen to indicate s is swept.
atomic.Store(&s.sweepgen, sg-1)
} else {
// Indicate that s is no longer cached.
atomic.Store(&s.sweepgen, sg)
} // Put the span in the appropriate place.
if stale {
// It's stale, so just sweep it. Sweeping will put it on
// the right list.
//
// We don't use a sweepLocker here. Stale cached spans
// aren't in the global sweep lists, so mark termination
// itself holds up sweep completion until all mcaches
// have been swept.
ss := sweepLocked{s}
ss.sweep(false)
} else {
if int(s.nelems)-int(s.allocCount) > 0 {
// Put it back on the partial swept list.
c.partialSwept(sg).push(s)
} else {
// There's no free space and it's not stale, so put it on the
// full swept list.
c.fullSwept(sg).push(s)
}
}
}
type mcentral struct {
spanclass spanClass partial [2]spanSet // list of spans with a free object
full [2]spanSet // list of spans with no free objects
}
mheap
// grow allocates a new empty span from the heap and initializes it for c's size class.
func (c *mcentral) grow() *mspan {
npages := uintptr(class_to_allocnpages[c.spanclass.sizeclass()])
size := uintptr(class_to_size[c.spanclass.sizeclass()]) s, _ := mheap_.alloc(npages, c.spanclass, true)
if s == nil {
return nil
} // Use division by multiplication and shifts to quickly compute:
// n := (npages << _PageShift) / size
n := s.divideByElemSize(npages << _PageShift)
s.limit = s.base() + size*n
heapBitsForAddr(s.base()).initSpan(s)
return s
}
func (h *mheap) alloc(npages uintptr, spanclass spanClass, needzero bool) (*mspan, bool) {
// Don't do any operations that lock the heap on the G stack.
// It might trigger stack growth, and the stack growth code needs
// to be able to allocate heap.
var s *mspan
systemstack(func() {
// To prevent excessive heap growth, before allocating n pages
// we need to sweep and reclaim at least n pages.
if !isSweepDone() {
h.reclaim(npages)
}
s = h.allocSpan(npages, spanAllocHeap, spanclass)
}) if s == nil {
return nil, false
}
isZeroed := s.needzero == 0
if needzero && !isZeroed {
memclrNoHeapPointers(unsafe.Pointer(s.base()), s.npages<<_PageShift)
isZeroed = true
}
s.needzero = 0
return s, isZeroed
}
func (h *mheap) allocSpan(npages uintptr, typ spanAllocType, spanclass spanClass) (s *mspan) {
// Function-global state.
gp := getg()
base, scav := uintptr(0), uintptr(0) // On some platforms we need to provide physical page aligned stack
// allocations. Where the page size is less than the physical page
// size, we already manage to do this by default.
needPhysPageAlign := physPageAlignedStacks && typ == spanAllocStack && pageSize < physPageSize // If the allocation is small enough, try the page cache!
// The page cache does not support aligned allocations, so we cannot use
// it if we need to provide a physical page aligned stack allocation.
pp := gp.m.p.ptr()
if !needPhysPageAlign && pp != nil && npages < pageCachePages/4 {
c := &pp.pcache // If the cache is empty, refill it.
if c.empty() {
lock(&h.lock)
*c = h.pages.allocToCache()
unlock(&h.lock)
} // Try to allocate from the cache.
base, scav = c.alloc(npages)
if base != 0 {
s = h.tryAllocMSpan()
if s != nil {
goto HaveSpan
}
// We have a base but no mspan, so we need
// to lock the heap.
}
}
// 代表pageCache能够使用的空间数,8x64一共是512kb空间
const pageCachePages = 8 * unsafe.Sizeof(pageCache{}.cache) // pageCache represents a per-p cache of pages the allocator can
// allocate from without a lock. More specifically, it represents
// a pageCachePages*pageSize chunk of memory with 0 or more free
// pages in it.
type pageCache struct {
base uintptr // base代表该虚拟内存的基线地址
// cache和scav都是起到位图标记的作用,cache主要是标记哪些内存位置已经被使用了,scav标记已经被清除的区域
// 用来加速垃圾未收,在垃圾回收一定条件下两个可以互换,提升分配和垃圾回收效率。
cache uint64 // 64-bit bitmap representing free pages (1 means free)
scav uint64 // 64-bit bitmap representing scavenged pages (1 means scavenged)
}
// For one reason or another, we couldn't get the
// whole job done without the heap lock.
lock(&h.lock) .................
if base == 0 {
// Try to acquire a base address.
base, scav = h.pages.alloc(npages)
if base == 0 {
if !h.grow(npages) {
unlock(&h.lock)
return nil
}
base, scav = h.pages.alloc(npages)
if base == 0 {
throw("grew heap, but no adequate free space found")
}
}
}
................ unlock(&h.lock)
//path: /usr/local/go/src/runtime/mheap.go type mheap struct {
lock mutex // spans: 指向mspans区域,用于映射mspan和page的关系
spans []*mspan // 指向bitmap首地址,bitmap是从高地址向低地址增长的
bitmap uintptr // 指示arena区首地址
arena_start uintptr // 指示arena区已使用地址位置
arena_used uintptr // 指示arena区末地址
arena_end uintptr central [67*2]struct {
mcentral mcentral
pad [sys.CacheLineSize - unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte
}
}
参考文章
- 图解Go语言内存分配:https://juejin.cn/post/6844903795739082760
- 内存分配器:https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-memory-allocator/
- 栈空间管理:https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-stack-management/
- 技术干货 | 理解 Go 内存分配:https://cloud.tencent.com/developer/article/1861429
- 一个简单的内存分配器:https://github.com/KatePang13/Note/blob/main/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E5%99%A8.md
- 编写自己的内存分配器:https://soptq.me/2020/07/18/mem-allocator/
- Go内存分配原来这么简单:https://segmentfault.com/a/1190000020338427
- 图解Go语言内存分配:https://juejin.cn/post/6844903795739082760
- TCMalloc介绍:https://blog.csdn.net/aaronjzhang/article/details/8696212
- TCMalloc解密:https://wallenwang.com/2018/11/tcmalloc/
- 图解TCMalloc:https://zhuanlan.zhihu.com/p/29216091
- 内存分配器:https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-memory-allocator/
- 进程与线程:https://baijiahao.baidu.com/s?id=1687308494061329777&wfr=spider&for=pc
- 内存分配器 (Memory Allocator):https://blog.csdn.net/weixin_30940783/article/details/97806139
Go内存管理一文足矣的更多相关文章
- Java内存管理-一文掌握虚拟机创建对象的秘密(九)
勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! [福利]JVM系列学习资源无套路赠送 回顾一下: 本文是接着上一篇内容:Java内存管 ...
- 一文洞悉JVM内存管理机制
前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习导图: 一.为什么要学习内存管理? Java与C++之间有一堵由内存动态分配和垃圾回收机制所围成的高墙,墙 ...
- 一文了解.Net的CLR、GC内存管理
一文了解.Net的CLR.GC内存管理 微软官方文档对内存管理和CLR的概述 什么是托管代码? 托管代码就是执行过程交由运行时管理的代码. 在这种情况下,相关的运行时称为公共语言运行时 (CLR),不 ...
- 一文带你彻底了解大数据处理引擎Flink内存管理
摘要: Flink是jvm之上的大数据处理引擎. Flink是jvm之上的大数据处理引擎,jvm存在java对象存储密度低.full gc时消耗性能,gc存在stw的问题,同时omm时会影响稳定性.同 ...
- 内存管理 & 内存优化技巧 浅析
内存管理 浅析 下列行为都会增加一个app的内存占用: 1.创建一个OC对象: 2.定义一个变量: 3.调用一个函数或者方法. 如果app占用内存过大,系统可能会强制关闭app,造成闪退现象,影响用户 ...
- 想全面理解JWT?一文足矣!
有篇关于JWT的文章,叫"JWT: The Complete Guide to JSON Web Tokens",写得全面细致.为了自己能更清晰理解并惠及更多人,我把它大致翻译了过 ...
- PHP扩展-生命周期和内存管理
1. PHP源码结构 PHP的内核子系统有两个,ZE(Zend Engine)和PHP Core.ZE负责将PHP脚本解析成机器码(也成为token符)后,在进程空间执行这些机器码:ZE还负责内存管理 ...
- Linux堆内存管理深入分析(下)
Linux堆内存管理深入分析 (下半部) 作者@走位,阿里聚安全 0 前言回顾 在上一篇文章中(链接见文章底部),详细介绍了堆内存管理中涉及到的基本概念以及相互关系,同时也着重介绍了堆中chunk分 ...
- Linux堆内存管理深入分析(上)
Linux堆内存管理深入分析(上半部) 作者:走位@阿里聚安全 0 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞 ...
随机推荐
- 论Hello World 有多少种输出方法:
论Hello World 有多少种输出方法: C: printf("Hello Word!"); C++: cout<<"Hello Word!"; ...
- USB与电池切换电路图
- 浅析Node与Element
起因 起因有二: 在看winter老师的分享:<一个前端的自我修养>时,有注意到这么一幅图,里面有写childNode和children属性. 昨天有学弟问起我,能否自己定义一个所有元素节 ...
- Chrome 53 Beta一些有意思的改动
原文链接: http://blog.chromium.org/2016...译者:Icarus邮箱:xdlrt0111@163.com 如果没有特殊说明的话,以下都是应用在Android,Chrome ...
- 【二次元的CSS】—— 纯CSS3做的能换挡的电扇
这次分享的电扇,和以往用css3画人物相比 多加了一点交互,就是电扇开关的地方,用到了一点点css3的 :checked +div 这个很少用到的选择器来实现的. GitHub传送门:https:// ...
- Codepen 每日精选(2018-3-31)
按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以打开原始页面. 制作像素画的画板https://codepen.io/abeatrize/... 纯 css 画的晚上的风 ...
- [FireshellCTF2020]ScreenShooter 1
此题关键在于理清逻辑,本地将url发送给服务器,服务器请求sereenshooter以后将结果返回 所以应该在服务器查看日志. 发现了PhantomJS 引擎一下 <!DOCTYPE html& ...
- jsp笔记---标签
<meta>标签 <meta> 标签提供了 HTML 文档的元数据.元数据不会显示在客户端,但是会被浏览器解析. META元素通常用于指定网页的描述,关键词,文件的最后修改时间 ...
- CSS样式写在JSP代码中的几种方法
1.行内样式. 可以直接把css代码写在现有的HTML标签元素的开始标签里面,并且css样式代码要写在style=" "双引号中才可以, 如: <p style=" ...
- Java线程内存模型-JVM-底层原理
public class Demo1 { private static boolean initFlag=false; public static void main(String[] args) t ...