首页WIN10问题浅入 .NET Core 中的内存和GC知识

浅入 .NET Core 中的内存和GC知识

时间2022-11-04 08:45:06发布分享专员分类WIN10问题浏览116

目录

托管代码

自动内存管理

参考资料:

【1】

【2】:

托管代码

在 .NET 中, CLR(Common Language Runtime) 负责提取托管代码并编译成机器语言,然后执行它microsoft net framework 2 0 。在此过程中,CLR 提供自动内存管理、安全边界、类型安全等服务,保证了代码安全。

托管代码指在其执行过程中由 CLR(Common Language Runtime) 管理的代码,托管代码是可在 .NET 上运行得一种高级语言(C#、F#等),编写的托管代码被编译后会被生成 中间语言(IL)microsoft net framework 2 0 。

CLR 有 .NET Core/.NET5+、Mono、.NET Framework 等实现,托管代码生成的文件(IL代码)不能被操作系统直接运行,需要 CLR 的实现(如 .NET5) 托管运行,托管过程中对其再次编译生成二进制代码(JIT编译)microsoft net framework 2 0 。

中间语言(IL)有时也称为公共中间语言 (CIL) 或 Microsoft 中间语言 (MSIL)microsoft net framework 2 0 。

自动内存管理

自动内存管理是 CLR 的功能之一,它可以为应用程序管理内存的分配和释放,托管代码被执行时,由 CLR 进行内存管理,保证了内存安全microsoft net framework 2 0 。

垃圾回收 GC

GC(garbage collector)中文译为垃圾回收器,.NET 中的 GC指的是 CLR 中的 自动内存管理器,GC 负责管理 .NET 程序的 内存分配和释放microsoft net framework 2 0 。

GC 的优点如下:

自动管理内存microsoft net framework 2 0 ,不必手动分配和释放;

高效管理托管堆上的对象;

智能回收对象microsoft net framework 2 0 ,清除内存;

内存安全:避免野指针、悬空指针等情况造成严重错误;

内存 物理内存

物理内存是物理内存条上的内存空间,是物理机器真实的容量大小microsoft net framework 2 0 。

虚拟内存

虚拟内存由操作系统进行支持,如 Windows 上的虚拟内存,Linux 上的交互空间,虚拟内存需要操作系统映射到真实的内存地址空间才能使用microsoft net framework 2 0 。虚拟内存调度方式有分页式、段式、段页式3种,读者感兴趣可自行查阅资料。

现代操作系统都采用了虚拟内存管理技术,通过对物理存储设备的抽象,操作系统调度外存当作内存使用,提供了比物理内存更大的内存范围microsoft net framework 2 0 。

这些存储设备组成的内存称为虚拟地址空间,而用户(开发者)接触到的地址是虚地址,并不是真实的物理地址microsoft net framework 2 0 。虚拟空间大大拓展了内存,使得系统可以同时运行多道程序而不“吃力”。

虚拟地址空间分为两部分:用户空间、内核空间,每个程序运行时的会消耗两种空间microsoft net framework 2 0 。在 Linux 中比例是 3:1,在 Windows 中是 2:2。

.NET 内存组成

.NET 中,内存分为非托管内存、托管内存microsoft net framework 2 0 。

.NET Core/.NET5+ 有一个称为 dotnet的驱动程序,此驱动程序用于 执行命令或运行 .NET 程序microsoft net framework 2 0 。当我们使用 dotnet 命令运行一个 .dll 文件时,操作系统会启动 dotnet 驱动程序,此时会分配操作系统内存资源、dotnet 驱动程序内存资源,这一部分即非托管资源,其中 dotnet 部分的内存包含了 CLR 等部件的内存。即使你并没有使用到 C/C++ 等非托管代码或者使用非托管资源,也会使用到非托管内存。

接下来 CLR 将初始化新进程,CLR 将为其分配托管内存( 托管堆),这段托管内存是一个连续的地址空间区域microsoft net framework 2 0 。.NET 安全代码只能使用托管内存,不能直接使用物理内存,垃圾收集器会为安全代码在托管堆上分配和释放虚拟内存。

显然, dotnet 的工作原理十分复杂,笔者没有能力讲清楚,感兴趣的读者可以自行查阅资料microsoft net framework 2 0 。

CLR 中的内存

微软 .NET CLR 文档中写道:By default, on 32-bit computers, each process has a 2-GB user-mode virtual address space.

即在 32 位系统中,.NET 进程会使用 2GB 的用户模式虚拟内存,其虚拟地址空间的表示范围是 0x00000000 到 0x7fff;而 64 位系统中,地址范围是 0x000'00000000 到0x7FFF'FFFFFFFF,约等于 16TBmicrosoft net framework 2 0 。

从以上信息,我们知道 .NET 程序会消耗比较多的虚拟内存,如果在 64 位操作系统上运行 .NET 程序,其用户模式虚拟地址空间可能远远大于 2GBmicrosoft net framework 2 0 。

编写一个 "c1" 程序microsoft net framework 2 0 ,其代码如下:

staticvoidMain( string[] args)

Console.WriteLine( "Hello World!");

Console.Read;

在 Linux 中使用 dotnet xx.dll 命令运行程序microsoft net framework 2 0 ,然后查看其占用的资源:

VIRTRES SHR S %CPU %MEM TIME+ COMMAND

3.1g 0.0g 0.0g S 0.3 0.3 0:00.83 dotnet

使用 dotnet-counters 查看 dotnet 进程:

GC Heap Size(MB) 0

Gen 0 GC Count(Count / 1 sec) 0

Gen 0 Size(B) 0

Gen 1 GC Count(Count / 1 sec) 0

Gen 1 Size(B) 0

Gen 2 GC Count(Count / 1 sec) 0

Gen 2 Size(B) 0

LOH Size(B) 0

注:使用 dotnet run 运行 .NET 项目,会出现 dotnet、c1 两个进程,可以看到会产生 dotnet 和 c1 两个进程,dotnet 是驱动程序,dotnet 启动后,CLR 会将. dll 程序集编译,并初始化启动一个进程microsoft net framework 2 0 。

CLR 中的虚拟地址空间需要位于一个地址块中,因为在请求虚拟内存分配时,虚拟内存管理器必须找到满足需求的单个可用块,例如就算存在大于 2GB 的虚拟地址空间,但如果不是连续的,则会分配失败microsoft net framework 2 0 。如果没有足够的可供保留的虚拟地址空间或可供提交的物理空间,则可能会用尽内存。

CLR 虚拟内存状态

CLR 中的虚拟内存可以有三种状态:

State

Deion

Free 可用

The block of memory has no references to it and is available for allocation. 内存块没有对它的引用microsoft net framework 2 0 ,可以进行分配

Reserved保留

The block of memory is available for your use and cannot be used for any other allocation request. 该内存块可供您使用microsoft net framework 2 0 ,不能用于任何其他分配请求 However, you cannot store data to this memory block until it is committed. 但是,在提交数据之前,不能将数据存储到此内存块中

Committed已提交

The block of memory is assigned to physical storage. 内存块已指派给物理存储

内存分配

CLR 在初始化新进程时,会为进程保留一个连续的地址空间区域,这个地址空间被称为托管堆microsoft net framework 2 0 。托管堆中维护着一个指针,最初此指针指向托管堆的基址,这个指针是向后移动的。当需要分配内存时,CLR 便会分配位于此指针后的内存区域,同时指针指向此对象地址空间之后的位置。

浅入 .NET Core 中的内存和GC知识

由于 CLR 通过向指针添加值来为对象分配内存,所以它的分配速度几乎跟从堆栈中分配内存速度一样快;而且连续分配的新对象连续存储在托管堆中,程序可以快速地访问这些对象microsoft net framework 2 0 。

当 GC 回收内存时,一些对象释放后内存会被回收,这样托管堆地内存处于碎片化,之后整个内存段会被压缩,重新组成连连续的内存段,指针会被重置到对象的末尾microsoft net framework 2 0 。

当然,大对象堆(LOH)回收并不会压缩内存段,这一点我们后面再讨论microsoft net framework 2 0 。

内存释放 垃圾回收的条件

根据微软官方文档microsoft net framework 2 0 ,整理的垃圾回收条件如下:

系统物理内存不足;

托管堆分配的内存已超出可接受阈值;(当然microsoft net framework 2 0 ,这个阈值会被动态调整)

手动调用 GC 类的 API(例如 GC.Collect);

托管堆 本机堆(Native Heap)

前面提到过,.NET 的内存有非托管内存和托管内存microsoft net framework 2 0 。CLR 运行的进程,存在本机堆和托管堆两种内存堆,本机内存堆通过 Windows API 的 VirtualAlloc 函数分配,提供给 操作系统和 CLR 使用,用于非托管代码所需的内存。

托管堆(Managed Heap)

关于托管堆,前面已经写了,这里不再赘述microsoft net framework 2 0 。

托管堆代数

托管堆中的内存被分为三代,分别使用0、1、2 标识,GC 分配的内存首先在 0 代托管堆中,当进行垃圾回收时,如果对象没有被释放,则将其升级并存储到 1 代托管堆中microsoft net framework 2 0 。1 代托管堆进行内存回收时,不被释放的对象也会被升级到 2 代内存中,然后 1 代内存堆进行空间压缩。

托管堆的管理是 GC 负责的,而 GC 进行内存分配和释放,使用了 GC 算法microsoft net framework 2 0 。

GC 算法基于以下理论:

① 压缩托管堆的一部分内存要比压缩整个托管堆速度快;

② 较新的对象生命周期较短microsoft net framework 2 0 ,较旧的对象生命周期较长;

③ 较新的对象趋向于相互关联microsoft net framework 2 0 ,并且大约在同一时间被应用程序访问;

我们必须深刻理解这些理论,才能深入理解托管堆的设计microsoft net framework 2 0 。

关于 0 到 2 代堆microsoft net framework 2 0 ,其基本说明如下:

0 代:0 代中的对象拥有短暂的生命周期microsoft net framework 2 0 ,垃圾回收最常发生在此代中;

1 代:作为生命周期较短和生命周期较长对象的缓冲区microsoft net framework 2 0 。

2 代:存储生命周期长的对象;0、1 代没被回收而升级的对象会升级到 2 代中,静态数据等则会一开始就分配到 2代microsoft net framework 2 0 。

在 .NET 5 之前microsoft net framework 2 0 ,.NET 有 SOH(小对象堆)、LOH(大对象堆);在 .NET 5 中,出现了 POH ;

小对象堆的内存段有 0、1、2 代堆;

今天就水到这里为止microsoft net framework 2 0 。

爱资源吧版权声明:以上文中内容来自网络,如有侵权请联系删除,谢谢。

CoreNETCoreNETmicrosoft net framework 2 0
内置IOC容器ServiceCollection 激活Folder for Mac(文件夹图标修改工具)