内存模型的变化
内存模型也是学习编程语言一个很重要的方面,随着程序的执行,内存模型时时刻刻都在发生着变化。学习一门编程语言需要有“时间”和“空间”上的认识,对于C#语言来说,“时间认识“从大的方面来说就是需要区分编译时和运行时的概念,”空间认识“即对内存模型和内存模型的实时变化有一个很好的了解。下面我们通过一个例子来看看内存模型的实时变化。下面先给出代码。
using System;
class Point
{
int x;
int y;
public Point(int x,int y)
{
this.x=x;
this.y=y;
}
public void Print()
{
Console.WriteLine("x={0},y={1}",this.x,this.y);
}
}
class Test
{
public static void Main()
{
int IntNum=100;
Console.WriteLine(IntNum);
Point p2=new Point(10,20);
for(int i=0;i<=100;i++)
{
if (i==100)
{
p2.Print();
}
}
}
}
首先,调用C#编译器编译这段代码为IL代码和元数据,生成名为Test.exe的程序集。
程序集编译好以后,接下来我们就可以编写执行指令来执行这段程序了,这时候进程开启,系统为当前进程分配一定的内存空间,接下来JIT编译器就开始对虚拟机指令(IL代码和元数据)进行即时编译了,根据JIT编译器的两大编译原则:1.以方法为单位整体编译。2.按需编译,只编译一次。JIT编译器首先找到入口点函数的地址,从这里开始进行编译,以上面的程序为例,根据两大原则,JIT编译器仅对Main函数进行了编译,而Print函数只是编译了对其的调用关系,并没有编译Print函数的实现,此时编译好的本地代码存储在当前进程的内存中。即时编译完成后,开始执行(运行时),此时内存模型开始建立,内存模型的建立离不开堆结构和栈结构,其中栈具有以下特征:1.数据的存放顺序为后进先出。2.与函数的执行紧密相关(其存在具有临时性)。3.自我销毁性。托管堆具有以下特点:1.具有全局性(共享)2.受垃圾收集器的管理。3.数据的存放具有连续性。由于Main函数的执行,此时在内存中形成了Main函数的栈以及存放在托管堆内存区实例对象p2。
当Main函数执行到for循环内部,且当i=100时,此时需要执行Print函数,但是此时却发现Print函数还没有经过编译,此时JIT编译器再一次启动并对Print函数进行编译,编译后的结果存储在当前进程的内存中,编译完成后,开始执行Print函数,此时就又需要为Print函数建立一个栈结构,因此内存模型发生了一次变化。当Print函数执行完毕以后,Print函数所对应的栈就自我销毁了。但是Print函数编译好的本地代码仍然还存在于当前进程的内存中,因此只要当前进程不关闭,如果Print函数再一次被执行,此时就不需要再经过JIT编译了,直接执行就可以了,而上次执行完以后Print函数对应的栈已经被销毁了,因此就需要从新建栈。
当Main函数执行完以后,程序结束,Main函数所对应的栈也自动销毁了,垃圾收集器会对托管堆上的对象进行收集,存放在当前进程内存中的本地代码内存也被清除了,最后进程关闭,当前进程的内存被回收。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架