.net 之内存回收

前言

一些基本概念如下:

托管代码

托管代码就是执行过程交由运行时管理的代码。 在这种情况下,相关的运行时称为公共语言运行时 (CLR),不管使用的是哪种实现(例如 Mono、.NET Framework 或 .NET Core/.NET 5+)。 CLR 负责提取托管代码、将其编译成机器代码,然后执行它。 除此之外,运行时还提供多个重要服务,例如自动内存管理、安全边界和类型安全。

非托管代码

如果运行 C/C++ 程序,则运行的代码也称为"非托管代码"。 在非托管环境中,程序员需要亲自负责处理相当多的事情。 实际的程序在本质上是操作系统 (OS) 载入内存,然后启动的二进制代码。 其他任何工作 - 从内存管理到安全考虑因素 - 对于程序员来说是一个不小的负担

非托管资源

使用完非托管资源后,必须显式释放这些资源。 最常用的非托管资源类型是包装操作系统资源的对象,如文件、窗口、网络连接或数据库连接

内存分配

每个进程都有其自己单独的虚拟地址空间。 同一台计算机上的所有进程共享相同的物理内存和页文件(如果有)

32 位计算机上的每个进程都具有 2 GB 的用户模式虚拟地址空间

  • 可能会存在虚拟地址空间碎片,这意味着地址空间中存在一些被称为孔的可用块。 当请求虚拟内存分配时,虚拟内存管理器必须找到满足该分配请求的足够大的单个可用块。 即使有 2 GB 可用空间,2 GB 分配请求也会失败,除非所有这些可用空间都位于一个地址块中。

  • 如果没有足够的可供保留的虚拟地址空间或可供提交的物理空间,则可能会用尽内存。

堆(Heap)

  • 堆内存是动态分配 的。通过调用newmalloc等方法显式地分配内存,需要显式释放(如freedelete)。堆适合存储更大、更复杂的数据结构(如对象、数组)。
  • 内存管理更加灵活,但需要手动管理或依赖垃圾回收器。
托管堆

初始化新进程时,运行时会为进程保留一个连续的地址空间区域。 这个保留的地址空间被称为托管堆。 托管堆维护着一个指针,用它指向将在堆中分配的下一个对象的地址。 最初,该指针设置为指向托管堆的基址。 托管堆上包含了所有引用类型。 应用程序创建第一个引用类型时,将为托管堆的基址中的类型分配内存。 应用程序创建下一个对象时,垃圾回收器在紧接第一个对象后面的地址空间内为它分配内存。

从托管堆中分配内存要比非托管内存分配速度快。 由于运行时通过为指针添加值来为对象分配内存,所以这几乎和从堆栈中分配内存一样快。

大对象堆(LOH)

大对象堆包含大小不少于 85,000 (约0.6M)个字节的对象,这些对象通常是数组。 非常大的实例对象是很少见的。

这个阀也是可以进行设置的

栈(Stack)

  • 栈内存是自动分配的。函数调用时,局部变量在栈上分配,函数结束后自动释放。栈内存通常很小,适合存放小的数据(如基本类型、指针)。
  • 内存分配方式是LIFO(后进先出),即最后放入的变量会最先释放。

GC回收的过程

垃圾回收器在执行回收时,会释放应用程序不再使用的对象的内存。 它通过检查应用程序的根来确定不再使用的对象。 每个应用程序都有一组根。 每个根或者引用托管堆中的对象,或者设置为空。 应用程序的根包含线程堆栈上的静态字段、局部变量和参数以及 CPU 寄存器。 垃圾回收器可以访问由实时 (JIT) 编译器和运行时维护的活动根的列表。 垃圾回收器对照此列表检查应用程序的根,并在此过程中创建一个图表,在其中包含所有可从这些根中访问的对象。

不在该图表中的对象将无法从应用程序的根中访问。 垃圾回收器会考虑无法访问的对象垃圾,并释放为它们分配的内存。 在回收中,垃圾回收器检查托管堆,查找无法访问对象所占据的地址空间块。 发现无法访问的对象时,它就使用内存复制功能来压缩内存中可以访问的对象,释放分配给不可访问对象的地址空间块。 在压缩了可访问对象的内存后,垃圾回收器就会做出必要的指针更正,以便应用程序的根指向新地址中的对象。 它还将托管堆指针定位至最后一个可访问对象之后。 请注意,只有在回收发现大量的无法访问的对象时,才会压缩内存。 如果托管堆中的所有对象均未被回收,则不需要压缩内存。

为了改进性能,运行时为单独堆中的大型对象分配内存。 垃圾回收器会自动释放大型对象的内存。 但是,为了避免移动内存中的大型对象,不会压缩此内存。

代数

垃圾回收主要在回收短生存期对象时发生。 为优化垃圾回收器的性能,将托管堆分为三代:第 0 代、第 1 代和第 2 代,因此它可以单独处理长生存期和短生存期对象。

第0代:

新分配的小对象构成新一代对象,并隐式地成为第 0 代集合,如果是大对象则为大型对象堆 (LOH),也称为第 3 代。

第1代:

如果第 0 代托管堆的回收没有回收足够的内存供应用程序创建新对象,垃圾回收器就会先执行第 1 代托管堆的回收,然后再执行第 2 代托管堆的回收。 第 1 级托管堆中未被回收的对象将会升级至第 2 级:

这一代包含长生存期对象。 长生存期对象的一个示例是服务器应用程序中的一个包含在进程期间处于活动状态的静态数据的对象。

第 2 代托管堆中未被回收的对象会继续保留在第 2 代托管堆中,直到在将来的回收中确定它们无法访问为止。 大对象堆也是在这一代进行会输

活动对象

垃圾回收器使用以下信息来确定对象是否为活动对象:

  • 堆栈根:由实时 (JIT) 编译器和堆栈查看器提供的堆栈变量。 JIT 优化可以延长或缩短报告给垃圾回收器的堆栈变量内的代码的区域。

  • 垃圾回收句柄:指向托管对象且可由用户代码或公共语言运行时分配的句柄。

静态数据:应用程序域中可能引用其他对象的静态对象。 每个应用程序域都会跟踪其静态对象

在垃圾回收启动之前,除了触发垃圾回收的线程以外的所有托管线程均会挂起。所以频繁的垃圾回收会造成性能的损失。

相关推荐
JosieBook3 小时前
【.NET全栈】ASP.NET实战—基于ASP.NET的求职系统设计与实现
后端·asp.net·.net
.Net Core 爱好者4 小时前
ASP .NET CORE 6 项目实现WebSocket通信实践
网络·websocket·网络协议·c#·.net·.netcore·visual studio
Lingbug11 小时前
MiniAuth - 轻量级.Net开源身份认证中间件
.net
时光追逐者13 小时前
一个.NET开发且功能强大的Windows远程控制系统
microsoft·c#·.net
翔云1234561 天前
Go语言的垃圾回收(GC)机制的迭代和优化历史
java·jvm·golang·gc
蓝黑20201 天前
Java知识点小结3:内存回收
java·gc
虚假程序设计2 天前
pythonnet python图像 C# .NET图像 互转
开发语言·人工智能·python·opencv·c#·.net
Zhen (Evan) Wang2 天前
.NET 6 API + Dapper + SQL Server 2014
数据库·c#·.net
俊哥V2 天前
[备忘]测算.net中对象所占用的内存
c#·.net·内存