概述
在.Net中,Clr帮助我们进行内存管理和垃圾回收,但这并不意味着我们不需要关心这些机制。为了优化程序的性能,在编写程序时必须考虑内存管理和垃圾回收。同时,了解内存管理机制可以帮助我们了解各种类型的变量的特征。在本文中,我们将介绍堆栈内存和堆内存的基础,以及各种类型的变量及其特性。
程序运行时,.Net会将程序信息存储在两个位置,堆内存和堆栈内存。它们只是内存的逻辑分段,在程序运行期间扮演着不同的角色,下面将对其进行详细说明。
Stack vs. Heap:有什么区别?
堆栈内存负责跟踪函数调用,而内存堆负责记录数据对象。您可以将堆栈视为一堆彼此堆叠的盒子。调用函数时,将在堆栈顶部堆积一个新框。我们只能使用顶盒并将其作为顶盒处理。完成后(函数调用完成),我们将其丢弃,然后继续处理当前位于堆栈顶部的框。
堆栈和堆存储内容
在程序执行过程中,主要需要在堆栈和堆上存储四种类型的数据:值类型,引用类型,指针和指令。
值类型
在C#中,从System.ValueType继承的所有类型都是值类型,包括:
引用类型
所有声明为以下类型的都是引用类型,包括:
指针
当我们将一个对象放入堆内存并访问该对象时,我们需要对该对象的引用。该引用通常是一个指针。我们不需要显式使用指针。 Clr将管理引用。
请注意指针(引用)和引用类型之间的区别。当我们将类型称为引用类型时,意味着需要通过指针对其进行访问。指针存储指向内存的地址。
命令
在程序执行期间,除了存储各种数据外,存储器还存储处理指令,例如变量声明,数学运算,跳转等,这些将在后面详细介绍。
要分配给堆栈还是堆?
对象内存分配遵循以下两个原则:
-引用类型总是分配给堆内存。
-值类型和指针的分配与它们的声明位置有关。
堆栈的主要功能之一是程执行时跟踪代码指针和正在调用的数据的位置。可以将其视为线程的状态,并且每个线程都有其自己的独立堆栈。调用函数时,函数的参数将被压入线程堆栈,方法中的局部变量也将被压入线程堆栈的顶部。这是最简单的示例。
public int AddFive(int pValue)
{
int result;
result = pValue + 5;
return result;
}让我们看一下调用AddFive方法时线程堆栈中的变化。
首先,在执行函数之前,将函数的实际参数推入线程堆栈的顶部。应该注意的是,该方法未放置程堆栈中,这里只是指示该方法调用的开始。
然后开始执行功能主体。该线程将控制指向AddFive指令的指令指针(在类型方法表中)。如果是第一次执行该功能,则将执行JIT编译,并将IL指令编译为本机CPU指令。
然后第一行声明一个局部变量结果,该函数的局部变量分配程堆栈上(注意:如果是引用类型,则将对象指针保存程堆栈上,下面的示例将进行讨论)。
执行该方法后,结果将作为返回值返回。 (该过程中涉及的指令的执行将在后续文章中进行解释)
通过在AddFive执行之前将指令执行指针移到某个地址,可以清除在AddFive执行期间分配的堆栈内存,然后返回到先前的方法(调用AddFive的方法,此处未显示)。
有时会将值类型分配给堆内存。记住值类型内存分配的原理:值类型内存的分配与变量声明的位置有关。例如,上面声明的局部变量是一个函数。分配给线程堆栈;如果在类中声明,则在堆内存中分配。看下面的例子:
//定义MyInt引用类型
public class MyInt
{
public int MyValue;
}
//定义示例函数
public MyInt AddFive(int pValue)
{
MyInt result = new MyInt();
result.MyValue = pValue +5;
return result;
}然后分析在AddFive调用期间的内存变化。
1.与以前相同,当线程开始调用该函数时,首先将实际参数推入线程堆栈的顶部。
接下来,进入功能主体。这里的开始与以前不同。它还声明一个结果变量。此时,由于result是引用类型,因此将其分配给堆内存。首先将指针推到线程堆栈,然后将MyInt实例分配给堆内存并返回内存地址,最后将指针指向返回的内存地址。
完成AddFive执行后,将清除线程堆栈内存。请注意,这只是清理堆栈内存,堆内存将由GC管理。
我们在堆内存上分配的MyInt,此时,没有指向它的变量,它已成为一块内存垃圾。垃圾回收时,GC会回收该内存。
在这里您可以看到垃圾收集(GC)的作用。它的作用是管理和回收堆内存。当GC开始调用时,它将暂停所有正在运行的线程,然后回收线程将检查内存。堆,在堆中标记出垃圾,然后删除垃圾以清理更多可用内存。可以想象,我们的堆内存是一个连续的内存地址。清理完垃圾后,它将导致内存碎片,因此最终GC将影响其余对象。位于其下的对象将被重定位,并且指向这些对象的所有指针(引用)将同时更新。就性能消耗而言,这一系列操作非常昂贵,因此在编写高性能代码时,请注意堆栈和堆的内存分配。
那么以上(分配在堆内存或堆栈内存中)对我们有什么影响?
使用引用类型时,我们处理的是指向实例对象的指针,而不是对象本身。当使用值类型时,我们将处理实例对象本身。让我们通过一个示例来了解差异。
public int ReturnValue()
{
int x = new int();
x = 3;
int y = new int();
y = x;
y = 4;
return x;
}这时的返回值为3.
以下使用MyInt实现上述代码:
public class MyInt
{
public int MyValue;
}
public int ReturnValue2()
{
MyInt x = new MyInt();
x.MyValue = 3;
MyInt y = new MyInt();
y = x;
y.MyValue = 4;
return x.MyValue;
}这时的返回值为4.
这是以上两个实例的堆栈情况:
第一个示例堆栈情况:
在第二个示例堆栈情况中,x和y都指向内存堆中的同一对象:
希望通过以上几个示例,我可以更好地理解C#值类型与引用类型之间的区别,以及指针(引用)的使用以及何时使用指针,包括函数过程调用,指令执行,GC等内容,仅作简单介绍,无需深入解释,我们将在后面详细解释其中的一些内容。在下一部分中,我们将进一步讨论与内存管理相关的内容,重点是方法参数。
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-373649-1.html