Design of memory management

The memory space has the heap area and the stack area. The stack generally stores local variables, method related data, and is automatically managed by the compiler. The heap is used to hold objects, and both Java and GO are collected by the garbage collector, eliminating the need to manually free and manage memory.

Memory management typically consists of three components

  • The application
  • Memory allocator
  • Garbage collector

Memory allocator

Applications apply for memory through the memory allocator, which initializes the appropriate memory region from the heap.

The methods of distributing the

Memory allocators typically contain two allocation methods

  • Linear distributor
  • Idle list allocator

Linear distributor

Java is the idea of using linear allocators.

With a linear allocator, you only need to maintain a pointer in memory, and if a user program asks for memory, the allocator just needs to move the pointer.

Simple and efficient memory method. There is a disadvantage, however, that after the pointer is moved, if previously allocated memory is reclaimed, it cannot be reallocated

Therefore, special collection algorithms such as tag compression, copy collection, and generational collection are required. They can defragment living objects by copying them, periodically merging free memory.

Idle list allocator

Go uses the idea of a free linked list allocator

The free list allocator maintains free memory through lists, ensuring that memory can be overused. But it’s very inefficient, O(n), because allocating memory requires you to go through the linked list and find the memory blocks that are larger than the allocated memory.

So there are different strategies for traversing memory, and there are four common ones

  • First-fit — Iterate from the top of the list and select the First memory block larger than the size of the requested memory.
  • Next Fit – Starts at the end of the last walk and selects the first memory block larger than the size of the allocated memory;
  • Best-fit — traverses the list from the head to select the most appropriate memory block;
  • Segregated Fit — Memory was Segregated into linked lists with blocks of the same size for each list, with the list satisfying the requirements being identified and the appropriate block from the list being selected for memory request;

The first three are relatively simple. Go uses a strategy similar to isolation adaptation, which reduces the number of memory blocks to traverse and improves the efficiency of memory allocation.

For example, if I divide the memory block into 2,4, and 8 bytes, then I apply for 6 bytes of memory, so I don’t need to traverse the list of 2,4 bytes.

Allocation mechanism

Go uses the thread cache allocation mechanism, the core idea of which is to classify objects by size and implement different allocation strategies based on the categories.

Go divides objects into three categories

  • Microobject (0, 16B)
  • Small object [16B, 32KB]
  • Large object (32KB, +∞)

Most of the objects in Go are small objects, which can improve the performance of the memory allocator by dealing with large objects separately.

The classification of objects here has nothing to do with the isolation adaptation described above, which corresponds to the MSPAN below

Components of the Go memory allocator

The allocator consists of three components: Thread Cache, Central Cache, and Page Heap. The corresponding structures are McAche, McEntral, and Mheap.

For an introduction to the three components, see the memory area section below.

mcache

Each worker thread is bound to a McAche, which caches the available Mspan resources, so it can be allocated directly to the Goroutine, so there is no competition and no lock consumption.

mcentral

Mspan resources for all McAche. Each Central holds a list of global MSpans of a specific size, both allocated and unallocated. Each McEntral corresponds to an Mspan, and the type of mspan results in the different sizes of the objects it splits. When the worker thread’s McAche does not have an appropriate mSpan (that is, a specific size), it is retrieved from McEntral.

McEntral is shared by all worker threads, and multiple Goroutines compete, thus consuming lock resources

mheap

Mheap: represents all heap space held by the Go program, which uses a global mheap object, _mheap, to manage heap memory.

When McEntral has no free MSpan, the mheap request is made. When mHEAP does not have resources, it asks the operating system for new memory.

Mheap is primarily used for allocating memory for large objects, and for managing MSPAN.

We also see that the Mheap contains all sizes of McEntral, so when a McAche requests an MSpan from a McEntral, it only needs to use the lock in the separate McEntral, and it does not affect the other sizes of mSpan.

Garbage collector

Garbage collection algorithm

Go uses the tag removal algorithm of traceable garbage collection

The GC process

  1. The cleanup termination phase suspends the program
  2. In the marking phase, write barrier is opened, GC is assisted, three-color marking method is adopted, root node joins the queue, and reference chain search is carried out
  3. The flag termination phase pauses the program and clears the processor’s thread cache
  4. During the cleanup phase, the write barrier is disabled and background concurrent cleanup is performed. Memory allocation also triggers cleanup

How to solve the problem of concurrent reachable objects disappearing

The object disappearance problem has two conditions

  1. A new reference to the black object to the white object was inserted
  2. The gray object reference to the white object was removed

Go solves both of these problems by inserting and deleting write barriers, coloring objects gray when they are added or updated.

Triggering of garbage collection

The background to trigger

The runtime starts a Goroutine in the background to force garbage collection when the application starts, and it sleeps most of the time, waking up periodically

Manual trigger

runtime.GC()
Copy the code

Application memory

  1. When there is no free space
  2. When applying for the allocation of a large object larger than 32kb

Go memory area

The heap

Prior to 1.10, Go used linear memory, that is, continuous memory space, but later versions have used sparse memory space instead of linear memory.

Linear memory

You can see that the memory is divided into three parts

  • spans
  • bitmap
  • arena

arena

The arena area is what we call the heap area, and it is in this area that Go dynamically allocates memory, 512GB. It splits memory into 8KB pages.

Here’s the concept of an mspan. An mspan is a collection of 8KB pages. We’ll talk more about mspan in a minute

bitmap

Bitmap is used to identify where objects are stored in arena. Each byte corresponds to the size of arenA4 pointer (pointer size is 8B), that is, each byte indicates whether 32 bytes in the heap are free

So the size of the bitmap region is 512GB/(4*8B)=16GB.

spans

Spans keep a pointer to mspan, so the size of your SPANS area is 512GB/8KB*8B=512MB

512GB/8KB is the number of pages, and *8B is the pointer size.

mspan

Here’s MSPAN, not to be confused with Spans.

Mspan, the basic unit of memory management in Go, is a large chunk of memory consisting of a contiguous 8KB page. The page here is not the same thing as the page of the operating system itself, which is typically several times the size of the operating system page.

In short: an MSPAN is a two-way linked list containing the starting address, the mspan size, the number of pages, and so on.

Spanclass And Sizeclass
type mspan struct{... spanclass spanClass ... }Copy the code

Because the mSPAN structure is so long, all subsequent code snippets capture only the important areas

Mspan has a property, called spanClass, that determines the size and number of objects mspan stores

Each Mspan is divided into several objects based on the size of sizeClass (not SpanClass). Each object can store one object.

Size_Class = Span_Class / 2

The size of an object is equal to sizeclass, and spanclass is twice the size of an object

There are 67 types of Size classes, each of which is a multiple of 8*2n. This is written in code.

Code location: Runtime /sizeclasses.go

// 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}

Copy the code

For example, if the Size Class is equal to 3, the object Size is 32B. 32B Object Stores objects whose sizes range from 17B to 32B. For small objects (less than 16B), the allocator merges them, allocating several objects into the same object. A size eclass of 0 represents a large object that needs to be allocated separately in the heap without mspan. That is, microobjects and small objects are allocated using mspan.

Because mspan is made up of pages and is an integer multiple of 8, there are different types of memory waste for different sizeclass types.

Sparse memory

  • Advantages: Using a sparse memory layout not only removes the upper limit on the heap size, but also resolves address space conflicts when C and Go are used together.
  • Disadvantages: Memory continuity is lost, making memory management more complex.

The stack

Escape analysis

The escaped object is allocated on the stack and the resource is reclaimed when the function returns, without the need for GC cleanup. Reduce the pile pressure