一、基本的数据结构

先看一下基本的内存数据结构:

//runtime/mheap.go
// minPhysPageSize is a lower-bound on the physical page size. The
// true physical page size may be larger than this. In contrast,
// sys.PhysPageSize is an upper-bound on the physical page size.
const minPhysPageSize = 4096type mheap struct {lock      mutexfree      [_MaxMHeapList]mSpanList // free lists of given lengthfreelarge mSpanList                // free lists length >= _MaxMHeapListbusy      [_MaxMHeapList]mSpanList // busy lists of large objects of given lengthbusylarge mSpanList                // busy lists of large objects length >= _MaxMHeapListsweepgen  uint32                   // sweep generation, see comment in mspansweepdone uint32                   // all spans are swept// allspans is a slice of all mspans ever created. Each mspan// appears exactly once.//// The memory for allspans is manually managed and can be// reallocated and move as the heap grows.//// In general, allspans is protected by mheap_.lock, which// prevents concurrent access as well as freeing the backing// store. Accesses during STW might not hold the lock, but// must ensure that allocation cannot happen around the// access (since that may free the backing store).allspans []*mspan // all spans out there// spans is a lookup table to map virtual address page IDs to *mspan.// For allocated spans, their pages map to the span itself.// For free spans, only the lowest and highest pages map to the span itself.// Internal pages map to an arbitrary span.// For pages that have never been allocated, spans entries are nil.//// This is backed by a reserved region of the address space so// it can grow without moving. The memory up to len(spans) is// mapped. cap(spans) indicates the total reserved memory.spans []*mspan// sweepSpans contains two mspan stacks: one of swept in-use// spans, and one of unswept in-use spans. These two trade// roles on each GC cycle. Since the sweepgen increases by 2// on each cycle, this means the swept spans are in// sweepSpans[sweepgen/2%2] and the unswept spans are in// sweepSpans[1-sweepgen/2%2]. Sweeping pops spans from the// unswept stack and pushes spans that are still in-use on the// swept stack. Likewise, allocating an in-use span pushes it// on the swept stack.sweepSpans [2]gcSweepBuf_ uint32 // align uint64 fields on 32-bit for atomics// Proportional sweeppagesInUse        uint64  // pages of spans in stats _MSpanInUse; R/W with mheap.lockspanBytesAlloc    uint64  // bytes of spans allocated this cycle; updated atomicallypagesSwept        uint64  // pages swept this cycle; updated atomicallysweepPagesPerByte float64 // proportional sweep ratio; written with lock, read without// TODO(austin): pagesInUse should be a uintptr, but the 386// compiler can't 8-byte align fields.// 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 heapbitmap         uintptr // Points to one byte past the end of the bitmapbitmap_mapped  uintptrarena_start    uintptrarena_used     uintptr // always mHeap_Map{Bits,Spans} before updatingarena_end      uintptrarena_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 mcentralpad      [sys.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.
}type mspan struct {next *mspan     // next span in list, or nil if noneprev *mspan     // previous span in list, or nil if nonelist *mSpanList // For debugging. TODO: Remove.startAddr     uintptr   // address of first byte of span aka s.base()npages        uintptr   // number of pages in spanstackfreelist gclinkptr // list of free stacks, avoids overloading freelist// freeindex is the slot index between 0 and nelems at which to begin scanning// for the next free object in this span.// Each allocation scans allocBits starting at freeindex until it encounters a 0// indicating a free object. freeindex is then adjusted so that subsequent scans begin// just past the the newly discovered free object.//// If freeindex == nelem, this span has no free objects.//// allocBits is a bitmap of objects in this span.// If n >= freeindex and allocBits[n/8] & (1<<(n%8)) is 0// then object n is free;// otherwise, object n is allocated. Bits starting at nelem are// undefined and should never be referenced.//// Object n starts at address n*elemsize + (start << pageShift).freeindex uintptr// TODO: Look up nelems from sizeclass and remove this field if it// helps performance.nelems uintptr // number of object in the span.// Cache of the allocBits at freeindex. allocCache is shifted// such that the lowest bit corresponds to the bit freeindex.// allocCache holds the complement of allocBits, thus allowing// ctz (count trailing zero) to use it directly.// allocCache may contain bits beyond s.nelems; the caller must ignore// these.allocCache uint64// allocBits and gcmarkBits hold pointers to a span's mark and// allocation bits. The pointers are 8 byte aligned.// There are three arenas where this data is held.// free: Dirty arenas that are no longer accessed//       and can be reused.// next: Holds information to be used in the next GC cycle.// current: Information being used during this GC cycle.// previous: Information being used during the last GC cycle.// A new GC cycle starts with the call to finishsweep_m.// finishsweep_m moves the previous arena to the free arena,// the current arena to the previous arena, and// the next arena to the current arena.// The next arena is populated as the spans request// memory to hold gcmarkBits for the next GC cycle as well// as allocBits for newly allocated spans.//// The pointer arithmetic is done "by hand" instead of using// arrays to avoid bounds checks along critical performance// paths.// The sweep will free the old allocBits and set allocBits to the// gcmarkBits. The gcmarkBits are replaced with a fresh zeroed// out memory.allocBits  *uint8gcmarkBits *uint8// 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 GCsweepgen    uint32divMul      uint16     // for divide by elemsize - divMagic.mulbaseMask    uint16     // if non-0, elemsize is a power of 2, & this will get object allocation baseallocCount  uint16     // capacity - number of objects in freelistsizeclass   uint8      // size classincache     bool       // being used by an mcachestate       mSpanState // mspaninuse etcneedzero    uint8      // needs to be zeroed before allocationdivShift    uint8      // for divide by elemsize - divMagic.shiftdivShift2   uint8      // for divide by elemsize - divMagic.shift2elemsize    uintptr    // computed from sizeclass or from npagesunusedsince int64      // first time spotted by gc in mspanfree statenpreleased  uintptr    // number of pages released to the oslimit       uintptr    // end of data in spanspeciallock mutex      // guards specials listspecials    *special   // linked list of special records sorted by offset.
}type gcBitsHeader struct {free uintptr // free is the index into bits of the next free byte.next uintptr // *gcBits triggers recursive type bug. (issue 14620)
}//go:notinheap
type gcBits struct {// gcBitsHeader // side step recursive type bug (issue 14620) by including fields by hand.free uintptr // free is the index into bits of the next free byte.next *gcBitsbits [gcBitsChunkBytes - gcBitsHeaderBytes]uint8
}var gcBitsArenas struct {lock     mutexfree     *gcBitsnext     *gcBitscurrent  *gcBitsprevious *gcBits
}

从上面的代码再比对Go的内存管理模型,就可以知道对应的内容。最基本的是Span,然后是Arena,GC是Bitmap。这在mehap的数据结构中都体现出来了。同样,为了保持和物理页的同步,一开始设置了minPhysPageSize = 4K,一般在X86上默认页的大小就是4K,当然在一些其它机器和最新的一些大页管理中,还是有不同的,但这并不妨碍最小的定义。

//runtime/mstats.go
// Statistics.
// If you edit this structure, also edit type MemStats below.
// Their layouts must match exactly.
//
// For detailed descriptions see the documentation for MemStats.
// Fields that differ from MemStats are further documented here.
//
// Many of these fields are updated on the fly, while others are only
// updated when updatememstats is called.
type mstats struct {// General statistics.alloc       uint64 // bytes allocated and not yet freedtotal_alloc uint64 // bytes allocated (even if freed)sys         uint64 // bytes obtained from system (should be sum of xxx_sys below, no locking, approximate)nlookup     uint64 // number of pointer lookupsnmalloc     uint64 // number of mallocsnfree       uint64 // number of frees// Statistics about malloc heap.// Protected by mheap.lock//// In mstats, heap_sys and heap_inuse includes stack memory,// while in MemStats stack memory is separated out from the// heap stats.heap_alloc    uint64 // bytes allocated and not yet freed (same as alloc above)heap_sys      uint64 // virtual address space obtained from systemheap_idle     uint64 // bytes in idle spansheap_inuse    uint64 // bytes in non-idle spansheap_released uint64 // bytes released to the osheap_objects  uint64 // total number of allocated objects// TODO(austin): heap_released is both useless and inaccurate// in its current form. It's useless because, from the user's// and OS's perspectives, there's no difference between a page// that has not yet been faulted in and a page that has been// released back to the OS. We could fix this by considering// newly mapped spans to be "released". It's inaccurate// because when we split a large span for allocation, we// "unrelease" all pages in the large span and not just the// ones we split off for use. This is trickier to fix because// we currently don't know which pages of a span we've// released. We could fix it by separating "free" and// "released" spans, but then we have to allocate from runs of// free and released spans.// Statistics about allocation of low-level fixed-size structures.// Protected by FixAlloc locks.stacks_inuse uint64 // this number is included in heap_inuse above; differs from MemStats.StackInusestacks_sys   uint64 // only counts newosproc0 stack in mstats; differs from MemStats.StackSysmspan_inuse  uint64 // mspan structuresmspan_sys    uint64mcache_inuse uint64 // mcache structuresmcache_sys   uint64buckhash_sys uint64 // profiling bucket hash tablegc_sys       uint64other_sys    uint64// Statistics about garbage collector.// Protected by mheap or stopping the world during GC.next_gc         uint64 // goal heap_live for when next GC ends; ^0 if disabledlast_gc         uint64 // last gc (in absolute time)pause_total_ns  uint64pause_ns        [256]uint64 // circular buffer of recent gc pause lengthspause_end       [256]uint64 // circular buffer of recent gc end times (nanoseconds since 1970)numgc           uint32numforcedgc     uint32  // number of user-forced GCsgc_cpu_fraction float64 // fraction of CPU time used by GCenablegc        booldebuggc         bool// Statistics about allocation size classes.by_size [_NumSizeClasses]struct {size    uint32nmalloc uint64nfree   uint64}// Statistics below here are not exported to MemStats directly.tinyallocs uint64 // number of tiny allocations that didn't cause actual allocation; not exported to go directly// gc_trigger is the heap size that triggers marking.//// When heap_live ≥ gc_trigger, the mark phase will start.// This is also the heap size by which proportional sweeping// must be complete.gc_trigger uint64// heap_live is the number of bytes considered live by the GC.// That is: retained by the most recent GC plus allocated// since then. heap_live <= heap_alloc, since heap_alloc// includes unmarked objects that have not yet been swept (and// hence goes up as we allocate and down as we sweep) while// heap_live excludes these objects (and hence only goes up// between GCs).//// This is updated atomically without locking. To reduce// contention, this is updated only when obtaining a span from// an mcentral and at this point it counts all of the// unallocated slots in that span (which will be allocated// before that mcache obtains another span from that// mcentral). Hence, it slightly overestimates the "true" live// heap size. It's better to overestimate than to// underestimate because 1) this triggers the GC earlier than// necessary rather than potentially too late and 2) this// leads to a conservative GC rate rather than a GC rate that// is potentially too low.//// Whenever this is updated, call traceHeapAlloc() and// gcController.revise().heap_live uint64// heap_scan is the number of bytes of "scannable" heap. This// is the live heap (as counted by heap_live), but omitting// no-scan objects and no-scan tails of objects.//// Whenever this is updated, call gcController.revise().heap_scan uint64// heap_marked is the number of bytes marked by the previous// GC. After mark termination, heap_live == heap_marked, but// unlike heap_live, heap_marked does not change until the// next mark termination.heap_marked uint64
}var memstats mstats// A MemStats records statistics about the memory allocator.
type MemStats struct {// General statistics.// Alloc is bytes of allocated heap objects.//// This is the same as HeapAlloc (see below).Alloc uint64// TotalAlloc is cumulative bytes allocated for heap objects.//// TotalAlloc increases as heap objects are allocated, but// unlike Alloc and HeapAlloc, it does not decrease when// objects are freed.TotalAlloc uint64// Sys is the total bytes of memory obtained from the OS.//// Sys is the sum of the XSys fields below. Sys measures the// virtual address space reserved by the Go runtime for the// heap, stacks, and other internal data structures. It's// likely that not all of the virtual address space is backed// by physical memory at any given moment, though in general// it all was at some point.Sys uint64// Lookups is the number of pointer lookups performed by the// runtime.//// This is primarily useful for debugging runtime internals.Lookups uint64// Mallocs is the cumulative count of heap objects allocated.// The number of live objects is Mallocs - Frees.Mallocs uint64// Frees is the cumulative count of heap objects freed.Frees uint64// Heap memory statistics.//// Interpreting the heap statistics requires some knowledge of// how Go organizes memory. Go divides the virtual address// space of the heap into "spans", which are contiguous// regions of memory 8K or larger. A span may be in one of// three states://// An "idle" span contains no objects or other data. The// physical memory backing an idle span can be released back// to the OS (but the virtual address space never is), or it// can be converted into an "in use" or "stack" span.//// An "in use" span contains at least one heap object and may// have free space available to allocate more heap objects.//// A "stack" span is used for goroutine stacks. Stack spans// are not considered part of the heap. A span can change// between heap and stack memory; it is never used for both// simultaneously.// HeapAlloc is bytes of allocated heap objects.//// "Allocated" heap objects include all reachable objects, as// well as unreachable objects that the garbage collector has// not yet freed. Specifically, HeapAlloc increases as heap// objects are allocated and decreases as the heap is swept// and unreachable objects are freed. Sweeping occurs// incrementally between GC cycles, so these two processes// occur simultaneously, and as a result HeapAlloc tends to// change smoothly (in contrast with the sawtooth that is// typical of stop-the-world garbage collectors).HeapAlloc uint64// HeapSys is bytes of heap memory obtained from the OS.//// HeapSys measures the amount of virtual address space// reserved for the heap. This includes virtual address space// that has been reserved but not yet used, which consumes no// physical memory, but tends to be small, as well as virtual// address space for which the physical memory has been// returned to the OS after it became unused (see HeapReleased// for a measure of the latter).//// HeapSys estimates the largest size the heap has had.HeapSys uint64// HeapIdle is bytes in idle (unused) spans.//// Idle spans have no objects in them. These spans could be// (and may already have been) returned to the OS, or they can// be reused for heap allocations, or they can be reused as// stack memory.//// HeapIdle minus HeapReleased estimates the amount of memory// that could be returned to the OS, but is being retained by// the runtime so it can grow the heap without requesting more// memory from the OS. If this difference is significantly// larger than the heap size, it indicates there was a recent// transient spike in live heap size.HeapIdle uint64// HeapInuse is bytes in in-use spans.//// In-use spans have at least one object in them. These spans// can only be used for other objects of roughly the same// size.//// HeapInuse minus HeapAlloc esimates the amount of memory// that has been dedicated to particular size classes, but is// not currently being used. This is an upper bound on// fragmentation, but in general this memory can be reused// efficiently.HeapInuse uint64// HeapReleased is bytes of physical memory returned to the OS.//// This counts heap memory from idle spans that was returned// to the OS and has not yet been reacquired for the heap.HeapReleased uint64// HeapObjects is the number of allocated heap objects.//// Like HeapAlloc, this increases as objects are allocated and// decreases as the heap is swept and unreachable objects are// freed.HeapObjects uint64// Stack memory statistics.//// Stacks are not considered part of the heap, but the runtime// can reuse a span of heap memory for stack memory, and// vice-versa.// StackInuse is bytes in stack spans.//// In-use stack spans have at least one stack in them. These// spans can only be used for other stacks of the same size.//// There is no StackIdle because unused stack spans are// returned to the heap (and hence counted toward HeapIdle).StackInuse uint64// StackSys is bytes of stack memory obtained from the OS.//// StackSys is StackInuse, plus any memory obtained directly// from the OS for OS thread stacks (which should be minimal).StackSys uint64// Off-heap memory statistics.//// The following statistics measure runtime-internal// structures that are not allocated from heap memory (usually// because they are part of implementing the heap). Unlike// heap or stack memory, any memory allocated to these// structures is dedicated to these structures.//// These are primarily useful for debugging runtime memory// overheads.// MSpanInuse is bytes of allocated mspan structures.MSpanInuse uint64// MSpanSys is bytes of memory obtained from the OS for mspan// structures.MSpanSys uint64// MCacheInuse is bytes of allocated mcache structures.MCacheInuse uint64// MCacheSys is bytes of memory obtained from the OS for// mcache structures.MCacheSys uint64// BuckHashSys is bytes of memory in profiling bucket hash tables.BuckHashSys uint64// GCSys is bytes of memory in garbage collection metadata.GCSys uint64// OtherSys is bytes of memory in miscellaneous off-heap// runtime allocations.OtherSys uint64// Garbage collector statistics.// NextGC is the target heap size of the next GC cycle.//// The garbage collector's goal is to keep HeapAlloc ≤ NextGC.// At the end of each GC cycle, the target for the next cycle// is computed based on the amount of reachable data and the// value of GOGC.NextGC uint64// LastGC is the time the last garbage collection finished, as// nanoseconds since 1970 (the UNIX epoch).LastGC uint64// PauseTotalNs is the cumulative nanoseconds in GC// stop-the-world pauses since the program started.//// During a stop-the-world pause, all goroutines are paused// and only the garbage collector can run.PauseTotalNs uint64// PauseNs is a circular buffer of recent GC stop-the-world// pause times in nanoseconds.//// The most recent pause is at PauseNs[(NumGC+255)%256]. In// general, PauseNs[N%256] records the time paused in the most// recent N%256th GC cycle. There may be multiple pauses per// GC cycle; this is the sum of all pauses during a cycle.PauseNs [256]uint64// PauseEnd is a circular buffer of recent GC pause end times,// as nanoseconds since 1970 (the UNIX epoch).//// This buffer is filled the same way as PauseNs. There may be// multiple pauses per GC cycle; this records the end of the// last pause in a cycle.PauseEnd [256]uint64// NumGC is the number of completed GC cycles.NumGC uint32// NumForcedGC is the number of GC cycles that were forced by// the application calling the GC function.NumForcedGC uint32// GCCPUFraction is the fraction of this program's available// CPU time used by the GC since the program started.//// GCCPUFraction is expressed as a number between 0 and 1,// where 0 means GC has consumed none of this program's CPU. A// program's available CPU time is defined as the integral of// GOMAXPROCS since the program started. That is, if// GOMAXPROCS is 2 and a program has been running for 10// seconds, its "available CPU" is 20 seconds. GCCPUFraction// does not include CPU time used for write barrier activity.//// This is the same as the fraction of CPU reported by// GODEBUG=gctrace=1.GCCPUFraction float64// EnableGC indicates that GC is enabled. It is always true,// even if GOGC=off.EnableGC bool// DebugGC is currently unused.DebugGC bool// BySize reports per-size class allocation statistics.//// BySize[N] gives statistics for allocations of size S where// BySize[N-1].Size < S ≤ BySize[N].Size.//// This does not report allocations larger than BySize[60].Size.BySize [61]struct {// Size is the maximum byte size of an object in this// size class.Size uint32// Mallocs is the cumulative count of heap objects// allocated in this size class. The cumulative bytes// of allocation is Size*Mallocs. The number of live// objects in this size class is Mallocs - Frees.Mallocs uint64// Frees is the cumulative count of heap objects freed// in this size class.Frees uint64}
}

这两个数据结构体在上面的说明里提到了,一损俱损,一荣俱荣,不能单独搞一个修改。他们的主要目的在于提供当前内存管理的数据状态。各种统计信息数据都在这两个数据结构体里有体现。注释已经很清楚,不再说明。

//runtime/mcache.go
//go:notinheap
type mcache struct {// The following members are accessed on every malloc,// so they are grouped here for better caching.next_sample int32   // trigger heap sample after allocating this many byteslocal_scan  uintptr // bytes of scannable heap allocated// Allocator cache for tiny objects w/o pointers.// See "Tiny allocator" comment in malloc.go.// tiny points to the beginning of the current tiny block, or// nil if there is no current tiny block.//// tiny is a heap pointer. Since mcache is in non-GC'd memory,// we handle it by clearing it in releaseAll during mark// termination.tiny             uintptrtinyoffset       uintptrlocal_tinyallocs uintptr // number of tiny allocs not counted in other stats// The rest is not accessed on every malloc.alloc [_NumSizeClasses]*mspan // spans to allocate fromstackcache [_NumStackOrders]stackfreelist// Local allocator stats, flushed during GC.local_nlookup    uintptr                  // number of pointer lookupslocal_largefree  uintptr                  // bytes freed for large objects (>maxsmallsize)local_nlargefree uintptr                  // number of frees for large objects (>maxsmallsize)local_nsmallfree [_NumSizeClasses]uintptr // number of frees for small objects (<=maxsmallsize)
}//runtime/mcentral.go
// The MCentral doesn't actually contain the list of free objects; the MSpan does.
// Each MCentral is two lists of MSpans: those with free objects (c->nonempty)
// and those that are completely allocated (c->empty).// Central list of free objects of a given size.
//
//go:notinheap
type mcentral struct {lock      mutexsizeclass int32nonempty  mSpanList // list of spans with a free object, ie a nonempty free listempty     mSpanList // list of spans with no free objects (or cached in an mcache)
}
//runtime/mfixalloc.go
// FixAlloc is a simple free-list allocator for fixed size objects.
// Malloc uses a FixAlloc wrapped around sysAlloc to manages its
// MCache and MSpan objects.
//
// Memory returned by fixalloc.alloc is zeroed by default, but the
// caller may take responsibility for zeroing allocations by setting
// the zero flag to false. This is only safe if the memory never
// contains heap pointers.
//
// The caller is responsible for locking around FixAlloc calls.
// Callers can keep state in the object but the first word is
// smashed by freeing and reallocating.
//
// Consider marking fixalloc'd types go:notinheap.
type fixalloc struct {size   uintptrfirst  func(arg, p unsafe.Pointer) // called first time p is returnedarg    unsafe.Pointerlist   *mlinkchunk  unsafe.Pointernchunk uint32inuse  uintptr // in-use bytes nowstat   *uint64zero   bool // zero allocations
}

而管理Span的数据结构为mcentral,它通过mcache这个数据结构来为线程申请内存时的缓存,这样就需要再操作锁的过程。而上面的mheap来管理所有的堆,这样一大一小,就把内存的管理搞定了。

二、流程代码分析

看完后基本的数据结构代码,就可以看一下内存管理的的流程了,从init开始:

//mgc.go
func gcinit() {if unsafe.Sizeof(workbuf{}) != _WorkbufSize {throw("size of Workbuf is suboptimal")}_ = setGCPercent(readgogc())memstats.gc_trigger = heapminimum// Compute the goal heap size based on the trigger://   trigger = marked * (1 + triggerRatio)//   marked = trigger / (1 + triggerRatio)//   goal = marked * (1 + GOGC/100)//        = trigger / (1 + triggerRatio) * (1 + GOGC/100)//当下一次 GC 结束后,堆内存的目标memstats.next_gc = uint64(float64(memstats.gc_trigger) / (1 + gcController.triggerRatio) * (1 + float64(gcpercent)/100))if gcpercent < 0 {memstats.next_gc = ^uint64(0)}work.startSema = 1work.markDoneSema = 1
}
//go:linkname setGCPercent runtime/debug.setGCPercent
func setGCPercent(in int32) (out int32) {lock(&mheap_.lock)out = gcpercentif in < 0 {in = -1}gcpercent = inheapminimum = defaultHeapMinimum * uint64(gcpercent) / 100if gcController.triggerRatio > float64(gcpercent)/100 {gcController.triggerRatio = float64(gcpercent) / 100}// This is either in gcinit or followed by a STW GC, both of// which will reset other stats like memstats.gc_trigger and// memstats.next_gc to appropriate values.unlock(&mheap_.lock)return out
}
func readgogc() int32 {p := gogetenv("GOGC")if p == "off" {return -1}if n, ok := atoi32(p); ok {return n}return 100
}

GC的初始化中,首先对缓冲区的大小进行控制,然后对GC的频率进行控制,也即对nextgc和触发进行比例分配(以100为基点“defaultHeapMinimum is the value of heapminimum for GOGC==100”)。在setGCPercent这个函数中可以看到,heapminimum 的计算方式,而这个计算结果就是后面的内存状态数据结构中的触发数据。
在这个版本中,触发、标记和目标被注释。一定要注意到GC设置中的锁保护,这是至关重要的问题,在前面数据结构中,英文注释中也提到了。内存操作,首要就是安全。初始化基本就是对一些参数状态的配置,然后就没有然后了。
初始化完成后,就可以启动GC了,看一下这个函数:

func gcStart(mode gcMode, forceTrigger bool) {// Since this is called from malloc and malloc is called in// the guts of a number of libraries that might be holding// locks, don't attempt to start GC in non-preemptible or// potentially unstable situations.mp := acquirem()if gp := getg(); gp == mp.g0 || mp.locks > 1 || mp.preemptoff != "" {releasem(mp)return}releasem(mp)mp = nil// Pick up the remaining unswept/not being swept spans concurrently//// This shouldn't happen if we're being invoked in background// mode since proportional sweep should have just finished// sweeping everything, but rounding errors, etc, may leave a// few spans unswept. In forced mode, this is necessary since// GC can be forced at any point in the sweeping cycle.//// We check the transition condition continuously here in case// this G gets delayed in to the next GC cycle.for (mode != gcBackgroundMode || gcShouldStart(forceTrigger)) && gosweepone() != ^uintptr(0) {sweep.nbgsweep++}// Perform GC initialization and the sweep termination// transition.//// If this is a forced GC, don't acquire the transition lock// or re-check the transition condition because we// specifically *don't* want to share the transition with// another thread.useStartSema := mode == gcBackgroundModeif useStartSema {semacquire(&work.startSema, 0)// Re-check transition condition under transition lock.if !gcShouldStart(forceTrigger) {semrelease(&work.startSema)return}}// For stats, check if this GC was forced by the user.forced := mode != gcBackgroundMode// In gcstoptheworld debug mode, upgrade the mode accordingly.// We do this after re-checking the transition condition so// that multiple goroutines that detect the heap trigger don't// start multiple STW GCs.if mode == gcBackgroundMode {if debug.gcstoptheworld == 1 {mode = gcForceMode} else if debug.gcstoptheworld == 2 {mode = gcForceBlockMode}}// Ok, we're doing it!  Stop everybody elsesemacquire(&worldsema, 0)if trace.enabled {traceGCStart()}if mode == gcBackgroundMode {gcBgMarkStartWorkers()}gcResetMarkState()now := nanotime()work.stwprocs, work.maxprocs = gcprocs(), gomaxprocswork.tSweepTerm = nowwork.heap0 = memstats.heap_livework.pauseNS = 0work.mode = modework.pauseStart = nowsystemstack(stopTheWorldWithSema)// Finish sweep before we start concurrent scan.systemstack(func() {finishsweep_m()})// clearpools before we start the GC. If we wait they memory will not be// reclaimed until the next GC cycle.clearpools()if mode == gcBackgroundMode { // Do as much work concurrently as possiblegcController.startCycle()work.heapGoal = memstats.next_gc// Enter concurrent mark phase and enable// write barriers.//// Because the world is stopped, all Ps will// observe that write barriers are enabled by// the time we start the world and begin// scanning.//// It's necessary to enable write barriers// during the scan phase for several reasons://// They must be enabled for writes to higher// stack frames before we scan stacks and// install stack barriers because this is how// we track writes to inactive stack frames.// (Alternatively, we could not install stack// barriers over frame boundaries with// up-pointers).//// They must be enabled before assists are// enabled because they must be enabled before// any non-leaf heap objects are marked. Since// allocations are blocked until assists can// happen, we want enable assists as early as// possible.setGCPhase(_GCmark)gcBgMarkPrepare() // Must happen before assist enable.gcMarkRootPrepare()// Mark all active tinyalloc blocks. Since we're// allocating from these, they need to be black like// other allocations. The alternative is to blacken// the tiny block on every allocation from it, which// would slow down the tiny allocator.gcMarkTinyAllocs()// At this point all Ps have enabled the write// barrier, thus maintaining the no white to// black invariant. Enable mutator assists to// put back-pressure on fast allocating// mutators.atomic.Store(&gcBlackenEnabled, 1)// Assists and workers can start the moment we start// the world.gcController.markStartTime = now// Concurrent mark.systemstack(startTheWorldWithSema)now = nanotime()work.pauseNS += now - work.pauseStartwork.tMark = now} else {t := nanotime()work.tMark, work.tMarkTerm = t, twork.heapGoal = work.heap0if forced {memstats.numforcedgc++}// Perform mark termination. This will restart the world.gcMarkTermination()}if useStartSema {semrelease(&work.startSema)}
}

而哪里开始调用这个函数呢:

//mgc.go
// GC runs a garbage collection and blocks the caller until the
// garbage collection is complete. It may also block the entire
// program.
func GC() {gcStart(gcForceBlockMode, false)
}

看看注释,“GC运行垃圾收集并阻塞调用程序,直到垃圾收集已完成。它还可能阻塞整个程序。”这是整个世界停止的节奏啊。
还有:

// Allocate an object of size bytes.
// Small objects are allocated from the per-P cache's free lists.
// Large objects (> 32 kB) are allocated straight from the heap.
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {......if shouldhelpgc && gcShouldStart(false) {gcStart(gcBackgroundMode, false)}return x
}
//mheap.go
//go:linkname runtime_debug_freeOSMemory runtime/debug.freeOSMemory
func runtime_debug_freeOSMemory() {gcStart(gcForceBlockMode, false)systemstack(func() { mheap_.scavenge(-1, ^uint64(0), 0) })
}//proc.go
func forcegchelper() {forcegc.g = getg()for {lock(&forcegc.lock)if forcegc.idle != 0 {throw("forcegc: phase error")}atomic.Store(&forcegc.idle, 1)goparkunlock(&forcegc.lock, "force gc (idle)", traceEvGoBlock, 1)// this goroutine is explicitly resumed by sysmonif debug.gctrace > 0 {println("GC forced")}gcStart(gcBackgroundMode, true)}
}

这几个调用的地方恰好印证了GO中何时启动垃圾回收,一种是在手动调用GC函数时,上面看到了吧;另外一个是在申请内存时,根据堆大小来调用;还是有一个是在强制GC时调用,也就是最后一个。
Go 程序启动后,会在后台run一个线程,定时运行runtime.sysmon 函数,它主要用来检查死锁、运行计时器、调度抢占、以及 GC 等状态。其通过test 函数来判断是否应该进行 GC。由于 GC 可能需要执行时间比较长,所以启动一个强制触发垃圾收集的 Goroutine 执行 forcegchelper 函数。但 forcegchelper 函数一般会被 goparkunlock 函数挂起,直到 sysmon 触发GC 校验通过,才会将该被挂起的 Goroutine 放转身到全局调度队列中等待被调度执行 GC。
在gcStart这个函数中,基本上把所有的GC过程都包含了。包括一些控制方式,这里要是继续分析下去,就是一个超长篇的代码分析了。挪到下面,把控制策略和具体的GC过程逐一分析。

三、总结

理论从哪里来?太祖说过:不是从天上掉下来的。理论是从实践活动中来。但自从人类有了书籍可以传承知识后,后人在学习的过程中,往往忽视了这些理论是从哪里来的。这在文学上往往有意无意被忽略。但是在计算机这种强理论和实践结合的领域上,则不得不重视,结果就是很多人感到不可理解,很高深的样子。所以在前面学习了很多GC的理论,在后面就要把理论和实践相结合起来。每个实践和理论不一定百分百相吻合,一定要特殊之处,但整体的间架结构一定不会有不同。通过理论来指导实践,再通过实践反过来验证并不断抽象新的理论。这才是一个否定之否定的过程,一个追求真理的过程。
世上万物,莫不如是!

golang的垃圾回收算法之二基本流程代码分析相关推荐

  1. 内存与垃圾回收——(十二)GC 日志分析

    文章目录 12_GC 日志分析 12.1_GC 常用参数 12.2_日志补充说明 12.3_举例解读日志中堆空间数据 12.4_日志分析工具 12_GC 日志分析 12.1_GC 常用参数 通过阅读 ...

  2. 【Android 内存优化】内存抖动 ( 垃圾回收算法总结 | 分代收集算法补充 | 内存抖动排查 | 内存抖动操作 | 集合选择 )

    文章目录 一. 垃圾回收算法总结 二. 分代收集算法补充 三. 查看 Java 虚拟机 四. 获取 Android 应用可使用最大内存 五. 内存抖动标志 六. 排查内存抖动 七. 常见的造成内存抖动 ...

  3. JVM学习笔记(二):垃圾回收、垃圾回收算法、垃圾回收器(Serial、Parallel、CMC、G1)、内存分配原则实战

    垃圾回收 一.判断对象是否可以被回收 1.引用计数计数法 内容:在对象中添加一个引用计数器,每当有一个地方引用它,计数器就加一:当引用失效时,计数器就减一:任何时刻计数器为零的对象都是不可能在被使用的 ...

  4. JVM调优理论篇_二、常用垃圾回收器(JVM10种垃圾回收器)以及垃圾回收算法

    JVM调优理论篇_二.常用垃圾回收器以及垃圾回收算法 前言 一.垃圾回收基础 1.什么场景下使用垃圾回收 2.垃圾回收发生在哪个区域? 3.对象在什么情况下会被回收?(如何判断一个对象是否该被回收) ...

  5. JVM调优(二)垃圾回收算法

    原文出处: pengjiaheng 可以从不同的的角度去划分垃圾回收算法: 按照基本回收策略分 引用计数(Reference Counting): 比较古老的回收算法.原理是此对象有一个引用,即增加一 ...

  6. golang GC垃圾回收机制

    ** golang GC垃圾回收 ** 垃圾回收(Garbage Collection,简称GC)是编程语言中提供的自动的内存管理机制,自动释放不需要的对象,让出存储器资源,无需程序员手动执行. Go ...

  7. 【Java 虚拟机原理】垃圾回收算法( Java VisualVM 工具 | 安装 Visual GC 插件 | 使用 Java VisualVM 分析 GC 内存 )

    文章目录 一.Java VisualVM 工具安装 Visual GC 插件 二.使用 Java VisualVM 分析 GC 内存 一.Java VisualVM 工具安装 Visual GC 插件 ...

  8. 【Java 虚拟机原理】垃圾回收算法 ( 标记-清除算法 | 复制算法 | 标记-整理算法 )

    文章目录 总结 一.标记-清除算法 二.复制算法 三.标记-整理算法 总结 常用的垃圾回收算法 : 标记-清除算法 ; 复制算法 ; 标记-整理算法 ; 这些算法没有好坏优劣之分 , 都有各自的 优势 ...

  9. 【Android 内存优化】垃圾回收算法 ( 分代收集算法 | Serial 收集器 | ParNew 收集器 | Parallel Scavenge 收集器 | CMS 并发标记清除收集器 )

    文章目录 一. 分代收集算法 二. 垃圾回收器 / 收集器 ( GC ) 三. 串行收集器 ( Serial ) 四. ParNew 收集器 五. Parallel Scavenge 收集器 六. C ...

  10. 【Android 内存优化】垃圾回收算法 ( 内存优化总结 | 常见的内存泄漏场景 | GC 算法 | 标记清除算法 | 复制算法 | 标记压缩算法 )

    文章目录 一. 内存优化总结 二. 常见的内存泄漏场景 三. 内存回收算法 四. 标记-清除算法 ( mark-sweep ) 五. 复制算法 六. 标记-压缩算法 一. 内存优化总结 内存泄漏原理 ...

最新文章

  1. 图的遍历[摘录自严长生老师的网站]
  2. 深度学习:反向传播(back-propagation)
  3. boost中unordered_map的用法
  4. Syntax error, parameterized types are only available if source level is 1.5 or greater
  5. python实例编程_python 编程实例 5
  6. mean技术栈 linux,“MEAN”技术栈开发web应用
  7. 产品设计体会(8001)产品经理工作手册
  8. Unity 动态更改鼠标样式
  9. [计算机网络】【网络设备】网关冗余和负载平衡 (HSRP)
  10. 最小函数依赖集,候选码,保持3NF依赖性的分解例题
  11. 面部识别科普(刷脸支付)
  12. css+js实现自动伸缩导航栏
  13. 华为服务器默认用户名和密码怎么修改,服务器默认用户名和密码
  14. matlab三维数据切片二维
  15. Win11如何开启Telnet客户端?
  16. 阿里云学生机1年114元限时活动(24岁以下都可以购买)
  17. JS中常用正则转换和信息验证的封装(80个)
  18. ATmega32U4 芯片 时钟研究
  19. 软件构造复习总结(4)
  20. 逻辑模型和物理模型的区别

热门文章

  1. 少儿编程 电子学会图形化编程等级考试Scratch四级真题解析(判断题)2022年3月
  2. python concat_python数据拼接: pd.concat
  3. java异常处理(Exception handing)机制
  4. php apply filters,WordPress学习——apply_filters()详解
  5. Word Embedding与Word2Vec
  6. C语言求卢卡斯序列,卢卡斯数列 斐波那契数列和卢卡斯数列!
  7. 某云盘下载工具(IDM,Aria2)速度测试
  8. Address localhost:8080 is already in use
  9. 迭代数据流分析中的逆后序(Reverse Postorder)
  10. TCR历史期刊为何受大家欢迎?