浅入 .NET Core 中的内存和GC知识
参考资料:
【1】https://docs.microsoft.com/zh-cn/dotnet/standard/managed-code
【2】:https://docs.microsoft.com/zh-cn/dotnet/standard/clr
托管代码
在 .NET 中, CLR(Common Language Runtime) 负责提取托管代码并编译成机器语言,然后执行它。在此过程中,CLR 提供自动内存管理、安全边界、类型安全等服务,保证了代码安全。
托管代码指在其执行过程中由 CLR(Common Language Runtime) 管理的代码,托管代码是可在 .NET 上运行得一种高级语言(C#、F#等),编写的托管代码被编译后会被生成 中间语言(IL)。
CLR 有 .NET Core/.NET5+、Mono、.NET Framework 等实现,托管代码生成的文件(IL代码)不能被操作系统直接运行,需要 CLR 的实现(如 .NET5) 托管运行,托管过程中对其再次编译生成二进制代码(JIT编译)。
中间语言(IL)有时也称为公共中间语言 (CIL) 或 Microsoft 中间语言 (MSIL)。
自动内存管理
自动内存管理是 CLR 的功能之一,它可以为应用程序管理内存的分配和释放,托管代码被执行时,由 CLR 进行内存管理,保证了内存安全。
垃圾回收
GC
GC(garbage collector)中文译为垃圾回收器,.NET 中的 GC 指的是 CLR 中的自动内存管理器,GC 负责管理 .NET 程序的内存分配和释放。
GC 的优点如下:
自动管理内存,不必手动分配和释放;
高效管理托管堆上的对象;
智能回收对象,清除内存;
内存安全:避免野指针、悬空指针等情况造成严重错误;
内存
物理内存
物理内存是物理内存条上的内存空间,是物理机器真实的容量大小。
虚拟内存
虚拟内存(Virtual Memory)是计算机操作系统进行内存管理的一种技术,它可以将多个硬件、非连续地址的碎片空间组合起来,形成进程上可识别的连续内存空间。
虚拟内存由操作系统进行支持,如 Windows 上的虚拟内存,Linux 上的交互空间,虚拟内存需要操作系统映射到真实的内存地址空间才能使用。虚拟内存调度方式有分页式、段式、段页式3种,读者感兴趣可自行查阅资料。
现代操作系统都采用了虚拟内存管理技术,通过对物理存储设备的抽象,操作系统调度外存当作内存使用,提供了比物理内存更大的内存范围。
这些存储设备组成的内存称为虚拟地址空间,而用户(开发者)接触到的地址是虚地址,并不是真实的物理地址。虚拟空间大大拓展了内存,使得系统可以同时运行多道程序而不“吃力”。
虚拟地址空间分为两部分:用户空间、内核空间,每个程序运行时的会消耗两种空间。在 Linux 中比例是 3:1,在 Windows 中是 2:2。
.NET 内存组成
.NET 中,内存分为非托管内存、托管内存。
.NET Core/.NET5+ 有一个称为 dotnet 的驱动程序,此驱动程序用于执行命令或运行 .NET 程序。当我们使用 dotnet 命令运行一个 .dll 文件时,操作系统会启动 dotnet 驱动程序,此时会分配操作系统内存资源、dotnet 驱动程序内存资源,这一部分即非托管资源,其中 dotnet 部分的内存包含了 CLR 等部件的内存。即使你并没有使用到 C/C++ 等非托管代码或者使用非托管资源,也会使用到非托管内存。
接下来 CLR 将初始化新进程,CLR 将为其分配托管内存(托管堆),这段托管内存是一个连续的地址空间区域。.NET 安全代码只能使用托管内存,不能直接使用物理内存,垃圾收集器会为安全代码在托管堆上分配和释放虚拟内存。
显然, dotnet 的工作原理十分复杂,笔者没有能力讲清楚,感兴趣的读者可以自行查阅资料。
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,约等于 16TB。
从以上信息,我们知道 .NET 程序会消耗比较多的虚拟内存,如果在 64 位操作系统上运行 .NET 程序,其用户模式虚拟地址空间可能远远大于 2GB。
编写一个 "c1" 程序,其代码如下:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
Console.Read();
}
在 Linux 中使用 dotnet xx.dll 命令运行程序,然后查看其占用的资源:
VIRT RES 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 程序集编译,并初始化启动一个进程。
CLR 中的虚拟地址空间需要位于一个地址块中,因为在请求虚拟内存分配时,虚拟内存管理器必须找到满足需求的单个可用块,例如就算存在大于 2GB 的虚拟地址空间,但如果不是连续的,则会分配失败。如果没有足够的可供保留的虚拟地址空间或可供提交的物理空间,则可能会用尽内存。
CLR 虚拟内存状态
CLR 中的虚拟内存可以有三种状态:
State | Description |
---|---|
Free 可用 | The block of memory has no references to it and is available for allocation. 内存块没有对它的引用,可以进行分配 |
Reserved保留 | The block of memory is available for your use and cannot be used for any other allocation request. 该内存块可供您使用,不能用于任何其他分配请求 However, you cannot store data to this memory block until it is committed. 但是,在提交数据之前,不能将数据存储到此内存块中 |
Committed已提交 | The block of memory is assigned to physical storage. 内存块已指派给物理存储 |
内存分配
CLR 在初始化新进程时,会为进程保留一个连续的地址空间区域,这个地址空间被称为托管堆。托管堆中维护着一个指针,最初此指针指向托管堆的基址,这个指针是向后移动的。当需要分配内存时,CLR 便会分配位于此指针后的内存区域,同时指针指向此对象地址空间之后的位置。
由于 CLR 通过向指针添加值来为对象分配内存,所以它的分配速度几乎跟从堆栈中分配内存速度一样快;而且连续分配的新对象连续存储在托管堆中,程序可以快速地访问这些对象。
当 GC 回收内存时,一些对象释放后内存会被回收,这样托管堆地内存处于碎片化,之后整个内存段会被压缩,重新组成连连续的内存段,指针会被重置到对象的末尾。
当然,大对象堆(LOH)回收并不会压缩内存段,这一点我们后面再讨论。
内存释放
垃圾回收的条件
根据微软官方文档,整理的垃圾回收条件如下:
- 系统物理内存不足;
- 托管堆分配的内存已超出可接受阈值;(当然,这个阈值会被动态调整)
- 手动调用 GC 类的 API(例如 GC.Collect);
托管堆
本机堆(Native Heap)
前面提到过,.NET 的内存有非托管内存和托管内存。CLR 运行的进程,存在本机堆和托管堆两种内存堆,本机内存堆通过 Windows API 的 VirtualAlloc 函数分配,提供给 操作系统和 CLR 使用,用于非托管代码所需的内存。
托管堆(Managed Heap)
关于托管堆,前面已经写了,这里不再赘述。
托管堆代数
托管堆中的内存被分为三代,分别使用0、1、2 标识,GC 分配的内存首先在 0 代托管堆中,当进行垃圾回收时,如果对象没有被释放,则将其升级并存储到 1 代托管堆中。1 代托管堆进行内存回收时,不被释放的对象也会被升级到 2 代内存中,然后 1 代内存堆进行空间压缩。
托管堆的管理是 GC 负责的,而 GC 进行内存分配和释放,使用了 GC 算法。
GC 算法基于以下理论:
- ① 压缩托管堆的一部分内存要比压缩整个托管堆速度快;
- ② 较新的对象生命周期较短,较旧的对象生命周期较长;
- ③ 较新的对象趋向于相互关联,并且大约在同一时间被应用程序访问;
我们必须深刻理解这些理论,才能深入理解托管堆的设计。
关于 0 到 2 代堆,其基本说明如下:
- 0 代:0 代中的对象拥有短暂的生命周期,垃圾回收最常发生在此代中;
- 1 代:作为生命周期较短和生命周期较长对象的缓冲区。
- 2 代:存储生命周期长的对象;0、1 代没被回收而升级的对象会升级到 2 代中,静态数据等则会一开始就分配到 2代。
在 .NET 5 之前,.NET 有 SOH(小对象堆)、LOH(大对象堆);在 .NET 5 中,出现了 POH ;
小对象堆的内存段有 0、1、2 代堆;
今天就水到这里为止。
浅入 .NET Core 中的内存和GC知识的更多相关文章
- Window中的内存地址(小知识)
现在的编辑器大部分工作都是内存管理托管型,所以很少直接对Window的内存地址直接管理了. Window中的内存地址主要是以16进制数字体现的,当操作系统为32位时,那么每个内存地址为2的32次方,也 ...
- 浅谈.Net Core中使用Autofac替换自带的DI容器
为什么叫 浅谈 呢?就是字面上的意思,讲得比较浅,又不是不能用(这样是不对的)!!! Aufofac大家都不陌生了,说是.Net生态下最优秀的IOC框架那是一点都过分.用的人多了,使用教程也十分丰富, ...
- 『浅入深出』MySQL 中事务的实现
在关系型数据库中,事务的重要性不言而喻,只要对数据库稍有了解的人都知道事务具有 ACID 四个基本属性,而我们不知道的可能就是数据库是如何实现这四个属性的:在这篇文章中,我们将对事务的实现进行分析,尝 ...
- 浅入Kubernetes(6):CKAD认证中的部署教程
目录 预设网络 kubeadm 安装 k8s 配置 calico 自动补全工具 状态描述 目前为止,笔者已经写了 5 篇关于 k8s 的文章,这一篇笔者将介绍 CKAD 认证官方课程中,如何部署 k8 ...
- 浅入深出之Java集合框架(中)
Java中的集合框架(中) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...
- .net core中的高效动态内存管理方案
.net core在新增的System.Buffers中引入了一大堆高效内存管理的类,如span和memory.内存池.本文今天这里介绍一个高效动态内存访问方案. ReadOnlySequenceSe ...
- 浅谈ASP.NET Core中IOC与DI的理解和使用
说起IOC和DI,使用过ASP.NET Core的人对这两个概念一定不陌生,早前,自己也有尝试过去了解这两个东西,但是一直觉得有点很难去理解,总觉得对其还是模糊不清,所以,趁着今天有空,就去把两个概念 ...
- 浅谈ASP.NET Core中的DI
DI的一些事 传送门马丁大叔的文章 什么是依赖注入(DI: Dependency Injection)? 依赖注入(DI)是一种面向对象的软件设计模式,主要是帮助开发人员开发出松耦合的应用程序 ...
- Linux就这个范儿 第15章 七种武器 linux 同步IO: sync、fsync与fdatasync Linux中的内存大页面huge page/large page David Cutler Linux读写内存数据的三种方式
Linux就这个范儿 第15章 七种武器 linux 同步IO: sync.fsync与fdatasync Linux中的内存大页面huge page/large page David Cut ...
随机推荐
- 派大星的烦恼MISC
挺有意思的杂项,python将二进制转图片的时候出现的图片不像二维码,想看题解的时候发现网上的大部分题解都是直接转发,更有意思了. 题目是派大星的烦恼,给了我们一张粉红图片,放进010editor里面 ...
- 百度网盘下载器:SpeedPan2.3.8
SpeedPan是款百度网盘资源下载工具,下载速度还行(至少比百度网盘快太多了),支持登录百度账号,也支持免登录下载.官网免费版的取消分享了,我从油管上看到了这个软件,分享给大家. 天翼云:https ...
- 题解-ARC058D Iroha Loves Strings
题面 ARC058D Iroha Loves Strings 给定 \(n\) 个字符串,从中选出若干个按给出顺序连接起来,总长等于 \(m\),求字典序最小的,保证有解. 数据范围:\(1\le n ...
- Spark3.0中Dates和Timestamps
Spark3.0使用的是预公历,而之前都是儒略历和公历的混合(即1582年之前的日期使用儒略历,1582年之后使用公历,java.sql.Date这个API用的就是这种,而Java8里使用java.t ...
- Git的使用与五大场景的运用
目录 一.Git的基础 1.Git的基本运作流程 (1) workspace->index->Repository (2) checkout (3) pull, push, fetch/c ...
- 公司项目适配IOS9总结
1.JSONKit 项目在xcode7 IOS9 开发环境上报错,不能进行JSONSring和JSONData的使用 .在真机上没有问题,在模拟器上put和post数据适合JSONKit报空对象野指针 ...
- 常见的 emit 实现 AOP demo
0. 前言 上接:思想无语言边界:以 cglib 介绍 AOP 在 java 的一个实现方式 作为第四篇,我们回顾一下 csharp 里面比较常见动态编织实现方式emit 内容安排如下: emit a ...
- DRF对Django请求响应做了技术升级
Django视图是用来处理请求和响应的,Django默认是按Form和Template来设计的,如果要处理以JSON格式为主的RESTful API,那么就需要对Django请求和响应的处理代码进行优 ...
- ECharts的基本使用与方法
ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器,底层依赖轻量级的矢量图形库 ZRender,提供直观,交互丰富,可高度 ...
- 工具-Redis-django存储session(99.6.4)
@ 目录 1.说明 安装 修改设置 2.测试 关于作者 1.说明 之前django的session默认是存在的数据库里面的,我们也可以把session存储在redis里面 安装 pip install ...