.Net托管堆布局

加载堆

主要是供CLR内部使用,作为承载程序的元数据。

  1. HighFrequencyHeap

    存放CLR高频使用的内部数据,比如MethodTable,MethodDesc.

通过is判断类型之间的继承关系,调用接口的方法和虚方法,都需要访问MethodTable

  1. LowFrequencyHeap

    存放CLR低频使用的内部数据,比如EEClass,ClassLoader.

GC信息与异常处理表,它们都只在发生时才访问,因此访问频率不高。

  1. StringLiteralMap

    字符串驻留池:https://www.cnblogs.com/lmy5215006/p/18494483

字符串对象本身存储在FOH堆中,String Literal Map只是一个索引

  1. StubHeap

    函数入口的代码堆
  2. CodeHeap

    JIT编译代码使用的内部堆,比如生成IL。
  3. VirtualCallStubHeap

    虚方法调用的内部堆

使用!eeheap -loader可以查看

眼见为实

新版sos呈现方式不一样,可以使用老版sos展示文中所述内容

托管堆

大家的老朋友了,不做过多解释,由GC统一管理的内存堆.一个.NET程序中所有的Domain都会共用一个托管堆

  1. SOH

    略略略
  2. LOH

    略略略
  3. POH

    固定对象专属的堆,比如非托管线程访问托管对象,就需要把对象固定起来,避免被GC回收造成非托管代码的访问违例.

使用!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 三个段。

对于SOH段有点特殊,因为段上面还有分代逻辑。包含0代和1代的对象只会分配在新分配的内存段上(临时段),剩下的每个段都是2代的段



可以看到,代只是一个逻辑概念,并没有独立的段空间。0,1,2代共享段空间。

.NET8的段结构

到了.NET 8,代已经不是一个逻辑概念,而是一个物理概念。

每个代都有了自己独立的段空间。

代机制

每当GC触发时,所有对象(非固定)都会进行升代,直到gen2为止。

  1. obj对象刚创建,为0代

    内存地址为0x00000263ee009528,0x01fb08000028>0x000001fb080b71e0>01fb080b9068 说明obj放在0代里

  2. 第一次GC,obj升为1代

    内存地址在1代空间范围内

  3. 第二次GC,obj升为2代

    内存地址在2代空间范围内

代边界

细心的朋友会发现一个盲点,就是obj刚刚创建的时候,0代内存起始点为0263ee000028,升为1代后,1代内存起始点也变为了0263ee000028,2代也同样。

这就引申出另一个概念,GC升代,不是简单的copy对象从0代到1代。而是移动代的边界。

每次GC触发时,代边界指针会在多个“地址段”上迁移,通过这种逻辑操作,达到性能的最高,可以观察上面的 Allocated 区,一会给了 0gen,一会又给了 1gen,一会又给了 2gen

LOH大对象堆

大对象堆存储所有>=85000byte的对象,但也是有例外。LOH堆上对象管理相对宽松,没有“代”机制,默认情况下也不会压缩。

例外1-32位环境下的double[]

        static void Main(string[] args)
{
double[] 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堆?

这主要跟内存对齐有关,double的未对齐访问非常昂贵,远远超过long,ulong,int。这对于64位环境来说不是问题,总是对SOH与LOH使用8byte对齐。但对于4字节对齐的32位环境。这就是个大问题了.

所以CLR开发团队决定将阈值大于1000的double放入LOH堆(LOH堆总是8byte对齐)。避免了double未对齐访问的巨大成本

例外2-StringInter与静态成员以及元数据

https://www.cnblogs.com/lmy5215006/p/18515971

参考此文,在.NET5之前没有POH堆,所以CLR内部使用的三个数组也会进入LOH堆。

三个数组分别为

  1. static对象的object[]
  2. 字符串池 object[]
  3. 元数据 RuntimeType object[]

其实很好理解,这些都是低频变化的内容,放在LOH堆上好过放在SOH堆。

POH堆

POH堆解决了什么问题?

从.NET5开始,CLR团队给pinned的对象单独放入一个段中,这样pinned对象不会和普通对象混在一起。导致大量细小Free空间。从而降低托管堆碎片化,也降低了代降级的频次。

有点遗憾的是,非托管代码造成的对象固定,并不会移动到POH堆中。因此代降级的现象依旧存在。

感觉未来微软可以重点优化这块,固定对象是GC速度最大的阻碍。

如何使用POH堆?

在.NET 8中,将对象放入POH堆是一种“有意为之”行为,必须调用 GC 类提供的 AllocateArray 和 AllocateUninitializedArray 方法并设置 pinned=true

FOH

FOH堆解决了什么问题?

在.NET8中,如果一个对象在创建的时候,就明确知道是“永生”对象,那就没必要纳入托管堆的管理范围,只会徒增GC的工作量。因此干脆把对象放在托管堆之外,来提高性能

常见的例子就是字符串的字面量(literal)

static对象布局

静态的基元类型(short,int,long) ,它的值本身并不存放在托管堆上。而是存放在Domain中的高频堆中

静态的引用类型则不同。真正的对象存放在托管堆上,再由POH中一个object[]持有,最后被高频堆中的m_pGCStatics所管理

Domain下每一个Module都维护了一个DomainLocalModule结构,静态变量放在该Module中

眼见为实:静态基元类型分配在高频堆上?

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



通过汇编得知,static a的地址为00007ff9a618e4a8



观察高频堆地址可以发现,00007FF9A6180000<00007ff9a618e4a8<00007FF9A6190000 。明显属于高频堆

眼见为实:静态引用类型分配在哪?

    internal class Program
{
public static Person person = new Person(); static void Main(string[] args)
{
var num = person.age; Console.WriteLine(num); Debugger.Break();
}
} public class Person
{
public int age = 12;
}
  1. 使用!gcwhere命令来查看person对象属于0代中,说明对象本身分配在托管堆

  2. 使用!gcroot命令查看它的引用根,发现它被一个object[]所持有

  3. 再查看object[]的所属代,可以看到该对象属于POH堆

  4. bp coreclr!JIT_GetSharedNonGCStaticBase_Helper 下断点来获取 DomainLocalModule 实例



    注意,这里我重新运行了一遍,所以object[]内存地址有变

字符串驻留池布局

关于字符串的不可变性,参考此文:https://www.cnblogs.com/lmy5215006/p/18494483

在.NET8之前,字符串驻留与静态引用类型处理模式无差别。

.NET 8加入FOH堆之后,会将编译期间就能确定的字符串放入FOH堆,以便提高GC性能。

眼见为实

        static void Main(string[] args)
{
var str1 = "hello FOH";//编译期间能确定
var str2 = Console.ReadLine();
string.Intern(str2);//运行期间才能确定 Console.WriteLine($"str1={str1},str2={str2}");
Debugger.Break();
}
  1. 编译期间能确定的,直接加入了FOH

  2. 运行期间确定,与静态引用类型处理流程一致



.NET Core 堆结构(Heap)底层原理浅谈的更多相关文章

  1. Java线上问题排查神器Arthas快速上手与原理浅谈

    前言 当你兴冲冲地开始运行自己的Java项目时,你是否遇到过如下问题: 程序在稳定运行了,可是实现的功能点了没反应. 为了修复Bug而上线的新版本,上线后发现Bug依然在,却想不通哪里有问题? 想到可 ...

  2. CSRF漏洞原理浅谈

    CSRF漏洞原理浅谈 By : Mirror王宇阳 E-mail : mirrorwangyuyang@gmail.com 笔者并未深挖过CSRF,内容居多是参考<Web安全深度剖析>.& ...

  3. CAS+SSO原理浅谈

    http://www.cnblogs.com/yonsin/archive/2009/08/29/1556423.htmlSSO 是一个非常大的主题,我对这个主题有着深深的感受,自从广州 UserGr ...

  4. JAVA CAS原理浅谈

    java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包.可见CAS的重要性. CAS CAS:Compare and Swap, 翻译成比较并交换. java.uti ...

  5. Java中的SPI原理浅谈

    在面向对象的程序设计中,模块之间交互采用接口编程,通常情况下调用方不需要知道被调用方的内部实现细节,因为一旦涉及到了具体实现,如果需要换一种实现就需要修改代码,这违反了程序设计的"开闭原则& ...

  6. 如何把Java代码玩出花?JVM Sandbox入门教程与原理浅谈

    在日常业务代码开发中,我们经常接触到AOP,比如熟知的Spring AOP.我们用它来做业务切面,比如登录校验,日志记录,性能监控,全局过滤器等.但Spring AOP有一个局限性,并不是所有的类都托 ...

  7. php模板原理PHP模板引擎smarty模板原理浅谈

    mvc是开发中的一个伟大的思想,使得开发代码有了更加清晰的层次,让代码分为了三层各施其职.无论是对代码的编写以及后期的阅读和维护,都提供了很大的便利. 我们在php开发中,视图层view是不允许有ph ...

  8. PHP的模板引擎smarty原理浅谈

    mvc是开发中的一个伟大的思想,使得开发代码有了更加清晰的层次,让代码分为了三层各施其职.无论是对代码的编写以及后期的阅读和维护,都提供了很大的便利. 我们在php开发中,视图层view是不允许有ph ...

  9. Docker 基础底层架构浅谈

    docker学习过程中,免不了需要学习下docker的底层技术,今天我们来记录下docker的底层架构吧! 从上图我们可以看到,docker依赖于linux内核的三个基本技术:namespaces.C ...

  10. P3377 【模板】左偏树(可并堆) 左偏树浅谈

    因为也是昨天刚接触左偏树,从头理解,如有不慎之处,跪请指教. 左偏树: 什 么是(fzy说)左偏树啊? 前置知识: 左偏树中dist:表示到右叶点(就是一直往右下找,最后一个)的距离,特别的,无右节点 ...

随机推荐

  1. SaaS业务架构:业务能力分析

    大家好,我是汤师爷~ 今天聊聊SaaS业务架构的业务能力分析. 业务能力概述 简单来说,业务能力是企业"做某事的能力". 业务能力描述了企业当前和未来应对挑战的能力,即企业能做什么 ...

  2. RDK X5首发上手体验!真的太帅啦!!!

    RDK X5首发上手体验!真的太帅啦!!! 本Blog同步发表于: 地瓜机器人开发者论坛: CSDN: 一年多以前无意中了解到了RDK X3,之后我便迅速的被地平线机器人开发者论坛(现在改名为了地瓜机 ...

  3. CCF CSP-S 2024 提高组初赛解析

    本解析不提供阅读程序与完善程序题目的代码,如有需要请通过 luogu.com.cn 相关链接 下载 如有谬误烦请指正 答案 AACBB BDABD ACBCD ✓××BC ✓✓✓BCC ✓×✓CAC ...

  4. 第147天:免杀对抗-C2远控篇&C&C++&ShellCode定性分析&生成提取&Loader加载模式&编译执行

    https://blog.csdn.net/qq_29948489/article/details/136180966 #C2远控-ShellCode-认知&环境 1.创建工程时关闭SDL检查 ...

  5. T2回家(home)题解

    T2回家(home) 现在啥也不是了,虽然会了逆元,但是对期望概率题还是一窍不通,赛时相当于只推出了 \(n=1\) 的情况,结果运用到所有情况,理所应当只有20分. 题目描述 小Z是个路痴.有一天小 ...

  6. 基于AIOps实现智慧园区极简IT运维

    随着物联网.云平台.大数据.人工智能等技术的发展,并逐步投入到智慧园区的建设,传统园区数字化转型加快.园区的形式包括产业园区.教育园区.制造业园区.科研园区.社区等等,园区形态不断演进和发展,园区网承 ...

  7. Windows应急响应-灰鸽子远控木马

    目录 应急背景 木马查杀 1.查看异常连接 2.根据端口号查看对应进程文件 3.排查异常服务 4.发现启动项 开始查杀 入侵排查 1.账号排查 2.查看服务 3.查看启动项 4.查看计划任务 5.网络 ...

  8. foobar2000 v1.6.16 汉化版(2023.04.19)

    foobar2000 v1.6.16 汉化版 -----------------------[软件截图]---------------------- -----------------------[软 ...

  9. 配置 ZRAM,实现 Linux 下的内存压缩,零成本低开销获得成倍内存扩增

    由于项目需求,笔者最近在一台 Linux 服务器上部署了 ElasticSearch 集群,却发现运行过程中经常出现查询速度突然降低的问题,登录服务器后发现是物理内存不足,导致机器频繁发生页面交换.由 ...

  10. linux内核 快速分片,技术|Linux slabtop命令——显示内核片缓存信息

    Linux内核需要为临时对象如任务或者设备结构和节点分配内存,缓存分配器管理着这些类型对象的缓存.现代Linux内核部署了该缓存分配器以持有缓存,称之为片.不同类型的片缓存由片分配器维护.本文集中讨论 ...