- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
主要是供CLR内部使用,作为承载程序的元数据.
通过is判断类型之间的继承关系,调用接口的方法和虚方法,都需要访问MethodTable 。
GC信息与异常处理表,它们都只在发生时才访问,因此访问频率不高.
字符串对象本身存储在FOH堆中,String Literal Map只是一个索引 。
使用!eeheap -loader可以查看 。
新版sos呈现方式不一样,可以使用老版sos展示文中所述内容 。
大家的老朋友了,不做过多解释,由GC统一管理的内存堆.一个.NET程序中所有的Domain都会共用一个托管堆 。
使用!eeheap -gc可以查看 。
.NET8推出来的一个新堆,用来存放永远不会被GC管理的永生对象,比如string 字面量。 简单来说,就是一个对象你都永远不会释放了,还放在托管堆就是浪费了。不如单独拎出来存.
https://www.cnblogs.com/lmy5215006/p/18515971 。
上述所说的各种堆,只是一个逻辑上的概念。作为内存的物理承载。由堆段(Heap Seg-ment)实现. 简单来说,段是托管堆的物理表示.
segment | begin | allocated | committed allocated size | committed size |
---|---|---|---|---|
段指针的对象地址 | 内存分配的起始点 | 内存分配的末尾点 | 已提交的分配大小 | 已提交的大小 |
堆只是一个抽象的概念,在物理上的表现形式为内存段,作为CLR细化堆的一种管理单位。多个段组成了堆.
在.NET 8 之前,段分为SOH,LOH,POH 三个段。 对于SOH段有点特殊,因为段上面还有分代逻辑。包含0代和1代的对象只会分配在新分配的内存段上(临时段),剩下的每个段都是2代的段 可以看到,代只是一个逻辑概念,并没有独立的段空间。0,1,2代共享段空间.
到了.NET 8,代已经不是一个逻辑概念,而是一个物理概念。 每个代都有了自己独立的段空间.
每当GC触发时,所有对象(非固定)都会进行升代,直到gen2为止.
细心的朋友会发现一个盲点,就是obj刚刚创建的时候,0代内存起始点为0263ee000028,升为1代后,1代内存起始点也变为了0263ee000028,2代也同样。 这就引申出另一个概念,GC升代,不是简单的copy对象从0代到1代。而是移动代的边界。 每次GC触发时,代边界指针会在多个“地址段”上迁移,通过这种逻辑操作,达到性能的最高,可以观察上面的 Allocated 区,一会给了 0gen,一会又给了 1gen,一会又给了 2gen 。
大对象堆存储所有>=85000byte的对象,但也是有例外。LOH堆上对象管理相对宽松,没有“代”机制,默认情况下也不会压缩.
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未对齐访问的巨大成本 。
https://www.cnblogs.com/lmy5215006/p/18515971 参考此文,在.NET5之前没有POH堆,所以CLR内部使用的三个数组也会进入LOH堆。 三个数组分别为 。
其实很好理解,这些都是低频变化的内容,放在LOH堆上好过放在SOH堆.
POH堆解决了什么问题? 从.NET5开始,CLR团队给pinned的对象单独放入一个段中,这样pinned对象不会和普通对象混在一起。导致大量细小Free空间。从而降低托管堆碎片化,也降低了代降级的频次.
有点遗憾的是,非托管代码造成的对象固定,并不会移动到POH堆中。因此代降级的现象依旧存在。 感觉未来微软可以重点优化这块,固定对象是GC速度最大的阻碍.
在.NET 8中,将对象放入POH堆是一种“有意为之”行为,必须调用 GC 类提供的 AllocateArray 和 AllocateUninitializedArray 方法并设置 pinned=true 。
FOH堆解决了什么问题? 在.NET8中,如果一个对象在创建的时候,就明确知道是“永生”对象,那就没必要纳入托管堆的管理范围,只会徒增GC的工作量。因此干脆把对象放在托管堆之外,来提高性能 。
常见的例子就是字符串的字面量(literal) 。
静态的基元类型(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;
}
使用!gcwhere命令来查看person对象属于0代中,说明对象本身分配在托管堆 。
使用!gcroot命令查看它的引用根,发现它被一个object[]所持有 。
再查看object[]的所属代,可以看到该对象属于POH堆 。
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();
}
编译期间能确定的,直接加入了FOH 。
运行期间确定,与静态引用类型处理流程一致 。
最后此篇关于.NETCore堆结构(Heap)底层原理浅谈的文章就讲到这里了,如果你想了解更多关于.NETCore堆结构(Heap)底层原理浅谈的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
服务架构进化论 原始分布式时代 一直以来,我可能和大多数的人认知一样,认为我们的服务架构的源头是单体架构,其实不然,早在单体系
序列化和反序列化相信大家都经常听到,也都会用, 然而有些人可能不知道:.net为什么要有这个东西以及.net frameword如何为我们实现这样的机制, 在这里我也是简单谈谈我对序列化和反序列化的
内容,是网站的核心所在。要打造一个受用户和搜索引擎关注的网站,就必须从网站本身的内容抓起。在时下这个网络信息高速发展的时代,许多低质量的信息也在不断地充斥着整个网络,而搜索引擎对一些高质量的内容
从第一台计算机问世到现在计算机硬件技术已经有了很大的发展。不管是现在个人使用的PC还是公司使用的服务器。双核,四核,八核的CPU已经非常常见。这样我们可以将我们程序分摊到多个计算机CPU中去计算,在
基本概念: 浅拷贝:指对象的字段被拷贝,而字段引用的对象不会被拷贝,拷贝对象和原对象仅仅是引用名称有所不同,但是它们共用一份实体。对任何一个对象的改变,都会影响到另外一个对象。大部分的引用类型,实
.NET将原来独立的API和SDK合并到一个框架中,这对于程序开发人员非常有利。它将CryptoAPI改编进.NET的System.Security.Cryptography名字空间,使密码服务摆脱
文件与文件流的区别(自己的话): 在软件开发过程中,我们常常把文件的 “读写操作” ,与 “创造、移动、复制、删除操作” 区分开来
1. 前言 单元测试一直都是"好处大家都知道很多,但是因为种种原因没有实施起来"的一个老大难问题。具体是否应该落地单元测试,以及落地的程度, 每个项目都有自己的情况。 本篇为
事件处理 1、事件源:任何一个HTML元素(节点),body、div、button 2、事件:你的操作 &
1、什么是反射? 反射 (Reflection) 是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。 Oracle 官方对
1、源码展示 ? 1
Java 通过JDBC获得连接以后,得到一个Connection 对象,可以从这个对象获得有关数据库管理系统的各种信息,包括数据库中的各个表,表中的各个列,数据类型,触发器,存储过程等各方面的信息。
可能大家谈到反射面部肌肉都开始抽搐了吧!因为在托管语言里面,最臭名昭著的就是反射!它的性能实在是太低了,甚至在很多时候让我们无法忍受。不过不用那么纠结了,老陈今天就来分享一下如何来优化反射!&nbs
1. 前言 最近一段时间一直在研究windows 驱动开发,简单聊聊。 对比 linux,windows 驱动无论是市面上的书籍,视频还是社区,博文以及号主,写的人很少,导
问题:ifndef/define/endif”主要目的是防止头文件的重复包含和编译 ========================================================
不知不觉.Net Core已经推出到3.1了,大多数以.Net为技术栈的公司也开始逐步的切换到了Core,从业也快3年多了,一直坚持着.不管环境
以前面试的时候经常会碰到这样的问题.,叫你写一下ArrayList.LinkedList.Vector三者之间的区别与联系:原先一直搞不明白,不知道这三者之间到底有什么区别?哎,惭愧,基础太差啊,木
目录 @RequestParam(required = true)的误区 先说结论 参数总结 @RequestParam(r
目录 FTP、FTPS 与 SFTP 简介 FTP FTPS SFTP FTP 软件的主动模式和被动模式的区别
1、Visitor Pattern 访问者模式是一种行为模式,允许任意的分离的访问者能够在管理者控制下访问所管理的元素。访问者不能改变对象的定义(但这并不是强制性的,你可以约定为允许改变)。对管
我是一名优秀的程序员,十分优秀!