Location>code7788 >text

NET Core Heap Structure (Heap) Underlying Principle Introduction

Popularity:949 ℃/2024-12-14 10:22:29

.Net托管堆布局

image

加载堆

主要是供CLR内部使用,Metadata as a host program。

  1. HighFrequencyHeap
    Stores internal data used by the CLR HF, such as MethodTable, MethodDesc.

The inheritance relationship between types is determined by is, and calling both methods and virtual methods of an interface requires access to the MethodTable

  1. LowFrequencyHeap
    Stores internal data used by the CLR at low frequency, such as EEClass, ClassLoader.

GC Information and Exception Handling tables, both of which are accessed only when they occur and are therefore accessed infrequently.

  1. StringLiteralMap
    String Resident Pools:/lmy5215006/p/18494483

The string object itself is stored in the FOH heap, and the String Literal Map is just an index.

  1. StubHeap
    Code heap for function entry
  2. CodeHeap
    Internal heap used by JIT compiled code, e.g. to generate IL.
  3. VirtualCallStubHeap
    Internal heap for virtual method calls

Using !eeheap -loader you can view the

seeing is believing

image

The new sos is presented differently, and you can use the old sos to display the content described in the text

custodian

Everyone's old friend, without explaining too much, a unified memory heap managed by the GC . All Domains in a .NET program share the same managed heap.

  1. SOH
    briefly
  2. LOH
    briefly
  3. POH
    Fixing the heap exclusive to an object, such as an unmanaged thread accessing a managed object, requires the object to be fixed to avoid being recycled by GC causing unmanaged code to beaccess violation.

Using !eeheap -gc you can view the

seeing is believing

image

frozen pile

NET8 introduced a new heap to hold immortal objects that will never be managed by GC, such as string literals.
Simply put, it's a waste to keep an object in the managed heap when you're never going to release it. It's better to store it separately.

seeing is believing

image

/lmy5215006/p/18515971

section

The various heaps described above are just a logical concept. As a physical bearer of memory. Realized by the heap segment (Heap Seg-ment).
Simply put, a segment is a physical representation of the managed heap.

image

seeing is believing

image

segment begin allocated committed allocated size committed size
Object address of the segment pointer Starting point of memory allocation End point of memory allocation Allocation size submitted Submitted size

SOH small object pile (computing)

The heap is just an abstract concept, physically represented as memory segments, as a kind of management unit for the CLR's refinement of the heap. Multiple segments make up the heap.

Segment structure prior to .NET8

Before .NET 8, segments were categorized as SOH, LOH, and POH.
It's a bit special for SOH segments, because there is also generation logic on top of the segments. Objects containing generation 0 and generation 1 will only be allocated on newly allocated memory segments (temporary segments), and each remaining segment is a generation 2 segment
image
As you can see, generation is just a logical concept and does not have a separate segment space. generations 0,1,2 share the segment space.

Segment structure of .NET8

By .NET 8, generation is no longer a logical concept, but a physical one.
Each generation got its own separate segment space.
image

generational mechanism

Whenever GC is triggered, all objects (non-fixed) are upgraded until gen2.

  1. The obj object has just been created and is generation 0.
    Memory address is 0x00000263ee009528,0x01fb08000028>0x000001fb080b71e0>01fb080b9068 Explanation of obj placed in generation 0
    image
  2. First GC, obj is upgraded to 1 generation.
    Memory address within 1 generation of space
    image
  3. Second GC, obj upgraded to generation 2
    Memory address within 2 generations of space
    image

generational boundary

Careful friends will find a blind spot, that is, when the obj has just been created, the 0 generation memory start point is 0263ee000028, after the promotion to 1 generation, the 1 generation memory start point has also changed to 0263ee000028, the same for generation 2.
This leads to another concept, GC ascending generations, not simply copying objects from generation 0 to generation 1. Rather, it is moving the boundaries of the generation.
Each time the GC is triggered, the generation boundary pointers will be migrated on multiple "address segments", through this logical operation, to achieve the highest performance, you can observe the Allocated area above, a moment to the 0gen, a moment to the 1gen, and a moment to the 2gen.

LOH Large Object Heap

The large object heap stores all >=85000byte objects, but there are exceptions. LOH heap object management is relatively loose, there is no "generation" mechanism, by default, will not be compressed.
image

Exception 1 - double[] in a 32-bit environment

        static void Main(string[] args)
        {
            double[] array1 = new double[999];
            ((array1));

            double[] array2 = new double[1000];
            ((array2));

            double[,] array3 = new double[32,32];
            ((array3));

            long[] array4 = new long[1000];
            ((array4));

            ();
            ();
        }

image

Here's a curious phenomenon in32-bit environmentUnder that, the size of array2 = 4b+4+4+1000*8=8012byte. far<=85000byte. Why is it allocated to the LOH heap?
This is mainly related to memory alignment, unaligned accesses to double are very expensive, much more than long, ulong, int. This is not a problem for 64 bit environments, which always use 8byte alignment for SOH and LOH. But for a 4-byte aligned 32-bit environment. This is a big problem.
So the CLR development team decided to put doubles with a threshold greater than 1000 into the LOH heap (the LOH heap is always 8byte aligned). Avoiding the huge cost of unaligned accesses for doubles

Exception 2-StringInter with static members and metadata

/lmy5215006/p/18515971
Referring to this article, there was no POH heap before .NET5, so the three arrays used internally by the CLR also go into the LOH heap.
The three arrays are

  1. static object object[]
  2. String pool object[]
  3. Metadata RuntimeType object[]

It's actually quite understandable that these are low-frequency changes that are better placed on the LOH heap than on the SOH heap.

POH heap

What problem does the POH pile solve?
Starting with .NET5, the CLR team gave pinned objects to be put into a separate segment so that pinned objects don't get mixed up with regular objects. Resulting in a lot of fine Free space. This reduces managed heap fragmentation and also reduces the frequency of generation downgrades.

Somewhat unfortunately, object fixation caused by unmanaged code does not move to the POH heap. Thus generation degradation remains.
It feels like Microsoft could focus on optimizing this piece in the future, with fixed objects being the biggest impediment to GC speed.
image

How do I use the POH heap?

In .NET 8, placing objects into the POH heap is an"Intentionally."behavior, you must call the AllocateArray and AllocateUninitializedArray methods provided by the GC class and set pinned=true.
image

FOH

What problem does the FOH pile solve?
In .NET8, if an object is created with the explicit knowledge that the"Eternal life."objects, then there is no need to include them in the managed heap, which only adds to the GC workload. So simply put the object outside the managed heap to improve performance
image

A common example is string literals (literals).

static object layout, objects that will not be recovered by GC1

A static primitive type (short, int, long) , whose value itself is not stored on the managed heap. Instead, they are stored in the Domain's high-frequency heap.
image

Static reference types are different. Real objects are stored on the managed heap, held by an object[] in the POH, and finally managed by m_pGCStatics in the HF heap.
image

Each Module under the Domain maintains a DomainLocalModule structure, and static variables are placed in that Module

Seeing is believing: static primitive type assignment on HF heap?

    internal class Program
    {
        static long age = 10086;
        static void Main(string[] args)
        {
            age = 12;
            ("done. " + age);
            ();
        }
    }

image
We know from assembly that the address of static a is00007ff9a618e4a8
image
Looking at the HF heap address you can see that 00007FF9A6180000<00007ff9a618e4a8<00007FF9A6190000 . Obviously belongs to the HF stack

Seeing is believing:Where are static reference types assigned?

    internal class Program
    {
        public static Person person = new Person();

        static void Main(string[] args)
        {
            var num = ;

            (num);

            ();
        }
    }

    public class Person
    {
        public int age = 12;
    }
  1. Use the !gcwhere command to see that the person object belongs in generation 0, indicating that the object itself is allocated in the managed heap
    image

  2. Using the !gcroot command to look at the root of its references, I see that it is held by an object[].
    image

  3. Then check the generation to which object[] belongs, you can see that the object belongs to the POH heap
    image

  4. bp coreclr!JIT_GetSharedNonGCStaticBase_Helper breakpoints to get DomainLocalModule instances
    image
    Note that here I've re-run it, so the object[] memory address has changed

String Resident Pool Layout, Objects that will not be Recycled by GC2

For string immutability, refer to this article:/lmy5215006/p/18494483

image

Prior to .NET8, string residency was indistinguishable from the static reference type handling model.
NET 8, with the addition of the FOH heap, puts strings that can be determined during compilation into the FOH heap in order to improve GC performance.

seeing is believing

        static void Main(string[] args)
        {
            var str1 = "hello FOH"; // can be determined during compilation
            var str2 = ();
            (str2);//can only be determined during runtime

            ($"str1={str1},str2={str2}");
            ();
        }
  1. What can be determined during compilation is added directly to the FOH
    image

  2. Determined during runtime, consistent with static reference type handling process
    image
    image