.Net托管堆布局 
加载堆 主要是供CLR内部使用,作为承载程序的元数据。
HighFrequencyHeap 通过is判断类型之间的继承关系,调用接口的方法和虚方法,都需要访问MethodTable
LowFrequencyHeap GC信息与异常处理表,它们都只在发生时才访问,因此访问频率不高。
StringLiteralMaphttps://www.cnblogs.com/lmy5215006/p/18494483  字符串对象本身存储在FOH堆中,String Literal Map只是一个索引
StubHeap CodeHeap VirtualCallStubHeap 使用!eeheap -loader可以查看
眼见为实 
新版sos呈现方式不一样,可以使用老版sos展示文中所述内容
托管堆 大家的老朋友了,不做过多解释,由GC统一管理的内存堆.一个.NET程序中所有的Domain都会共用一个托管堆
SOH LOH POH访问违例 . 使用!eeheap -gc可以查看
眼见为实 
冻结堆 .NET8推出来的一个新堆,用来存放永远不会被GC管理的永生对象,比如string 字面量。
眼见为实 
https://www.cnblogs.com/lmy5215006/p/18515971 
段 上述所说的各种堆,只是一个逻辑上的概念。作为内存的物理承载。由堆段(Heap Seg-ment)实现.
眼见为实 
segment begin allocated committed allocated size committed size 段指针的对象地址 内存分配的起始点 内存分配的末尾点 已提交的分配大小 已提交的大小 
SOH小对象堆 堆只是一个抽象的概念,在物理上的表现形式为内存段,作为CLR细化堆的一种管理单位。多个段组成了堆。
.NET8之前的段结构 在.NET 8 之前,段分为SOH,LOH,POH 三个段。
.NET8的段结构 到了.NET 8,代已经不是一个逻辑概念,而是一个物理概念。
代机制 每当GC触发时,所有对象(非固定)都会进行升代,直到gen2为止。
obj对象刚创建,为0代 第一次GC,obj升为1代 第二次GC,obj升为2代 代边界 细心的朋友会发现一个盲点,就是obj刚刚创建的时候,0代内存起始点为0263ee000028,升为1代后,1代内存起始点也变为了0263ee000028,2代也同样。
LOH大对象堆 大对象堆存储所有>=85000byte的对象,但也是有例外。LOH堆上对象管理相对宽松,没有“代”机制,默认情况下也不会压缩。
例外1-32位环境下的double[]         static  void  Main (string [] argsdouble [] array1 = new  double [999 ];
            Console.WriteLine(GC.GetGeneration(array1));
            double [] array2 = new  double [1000 ];
            Console.WriteLine(GC.GetGeneration(array2));
            double [,] array3 = new  double [32 ,32 ];
            Console.WriteLine(GC.GetGeneration(array3));
            long [] array4 = new  long [1000 ];
            Console.WriteLine(GC.GetGeneration(array4));
            Debugger.Break();
            Console.ReadKey();
        }
这里有个很奇怪的现象,在32位环境 下,array2的大小= 4b+4+4+1000*8=8012byte. 远远<=85000byte. 为什么被分配到了LOH堆?
例外2-StringInter与静态成员以及元数据 https://www.cnblogs.com/lmy5215006/p/18515971 
static对象的object[] 字符串池 object[] 元数据 RuntimeType object[] 其实很好理解,这些都是低频变化的内容,放在LOH堆上好过放在SOH堆。
POH堆 POH堆解决了什么问题?
有点遗憾的是,非托管代码造成的对象固定,并不会移动到POH堆中。因此代降级的现象依旧存在。
如何使用POH堆? 在.NET 8中,将对象放入POH堆是一种“有意为之” 行为,必须调用 GC 类提供的 AllocateArray 和 AllocateUninitializedArray 方法并设置 pinned=true
FOH FOH堆解决了什么问题?“永生” 对象,那就没必要纳入托管堆的管理范围,只会徒增GC的工作量。因此干脆把对象放在托管堆之外,来提高性能
常见的例子就是字符串的字面量(literal)
static对象布局,不会被GC回收的对象1 静态的基元类型(short,int,long) ,它的值本身并不存放在托管堆上。而是存放在Domain中的高频堆中
静态的引用类型则不同。真正的对象存放在托管堆上,再由POH中一个object[]持有,最后被高频堆中的m_pGCStatics所管理
Domain下每一个Module都维护了一个DomainLocalModule结构,静态变量放在该Module中
眼见为实:静态基元类型分配在高频堆上?     internal  class  Program 
    {
        static  long  age = 10086 ;
        static  void  Main (string [] args12 ;
            Console.WriteLine("done. "  + age);
            Debugger.Break();
        }
    }
00007ff9a618e4a8 00007ff9a618e4a8 <00007FF9A6190000 。明显属于高频堆
眼见为实:静态引用类型分配在哪?     internal  class  Program 
    {
        public  static  Person person = new  Person();
        static  void  Main (string [] argsvar  num = person.age;
            Console.WriteLine(num);
            Debugger.Break();
        }
    }
    public  class  Person 
    {
        public  int  age = 12 ;
    }
使用!gcwhere命令来查看person对象属于0代中,说明对象本身分配在托管堆
使用!gcroot命令查看它的引用根,发现它被一个object[]所持有
再查看object[]的所属代,可以看到该对象属于POH堆
bp coreclr!JIT_GetSharedNonGCStaticBase_Helper 下断点来获取 DomainLocalModule 实例
字符串驻留池布局,不会被GC回收的对象2 关于字符串的不可变性,参考此文:https://www.cnblogs.com/lmy5215006/p/18494483 
在.NET8之前,字符串驻留与静态引用类型处理模式无差别。
眼见为实         static  void  Main (string [] argsvar  str1 = "hello FOH" ;
            var  str2 = Console.ReadLine();
            string .Intern(str2);
            Console.WriteLine($"str1={str1} ,str2={str2} " );
            Debugger.Break();
        }
编译期间能确定的,直接加入了FOH
运行期间确定,与静态引用类型处理流程一致
转自https://www.cnblogs.com/lmy5215006/p/18583743