继承:.NET中栈和堆的比较的三-四

今日将这多重之稿子转载了,希望对谢兴趣之爱人有所助!

.NET中栈和堆的比较三

原文出处 http://www.c-sharpcorner.com/UploadFile/rmcochran/chsarp\_memory401152006094206AM/chsarp\_memory4.aspx

尽管在.NET framework下我们并不需要担心内存管理暨污染源回收(Garbage
Collection),但是咱要应了解其,以优化我们的应用程序。同时,还用有一些基础的内存管理工作机制的学问,这样能够推进解释我们司空见惯程序编制中的变量的行。在本文中我们以涉及到堆中引用变量引起的题目,以及哪采取ICloneable接口来缓解该问题。

消回顾堆栈基础,值类型和援类型,请转至率先片和第二片段


* 副本并无是真副本

为了掌握的发明问题,让我们来比一下当堆中在值类型和援类型时犹发出了头什么。首先来瞧值类型,如下面的类和组织。这里出一个类Dude,它的积极分子中有一个string型的Name字段及少独Shoe类型的字段–RightShoe、LeftShoe,还有一个CopyDude()方法可死容易地挺成新的Dude实例。

public struct Shoe{

public string Color;

}

public class Dude

{

public string Name;

public Shoe RightShoe;

public Shoe LeftShoe;

public Dude CopyDude()

{

Dude newPerson = new Dude();

newPerson.Name = Name;

newPerson.LeftShoe = LeftShoe;

newPerson.RightShoe = RightShoe;

return newPerson;

}

public override string ToString()

{

return (Name + ” : Dude!, I have a ” + RightShoe.Color +

” shoe on my right foot, and a ” +

LeftShoe.Color + ” on my left foot.”);

}

}

Dude是引用类型,而且由于组织Shoe的一定量只字段是Dude类的积极分子,所以其还于在了堆上。

图片 1

当我们实践以下的方式时:

public static void Main()

{

Class1 pgm = new Class1();

Dude Bill = new Dude();

Bill.Name = “Bill”;

Bill.LeftShoe = new Shoe();

Bill.RightShoe = new Shoe();

Bill.LeftShoe.Color = Bill.RightShoe.Color = “Blue”;

Dude Ted = Bill.CopyDude();

Ted.Name = “Ted”;

Ted.LeftShoe.Color = Ted.RightShoe.Color = “Red”;

Console.WriteLine(Bill.ToString());

Console.WriteLine(Ted.ToString());

}

咱俩得了预想的结果:

Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left
foot.
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left
foot.

假如我们以组织Shoe换成引用类型会产生啊?问题即在这个。
假使我们拿Shoe改也援类型:

public class Shoe{

public string Color;

}

下一场在同前面相同之Main()方法中运行,再来探视我们的结果:

Bill : Dude!, I have a Red shoe on my right foot, and a Red on my left
foot
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left
foot

得视吉利鞋子被通过到别人(Bill)脚上了,很明显出错了。你想掌握这是干什么呢?我们再次来探堆就亮了。

图片 2

由于我们现使的Shoe是引用类型而无值类型,当引用类型的情节为拷贝时实际上只是拷贝了该类型的指针(并没拷贝实际的靶子),我们需要发一些格外的办事来要我们的援类型能如值类型一样采取。

有幸的是.NET
Framework中既生矣一个IClonealbe接口(System.ICloneable)来救助我们解决问题。使用这接口可以确定具有的Dude类必须信守和概念引用类型应什么为复制,以避免出现”共享鞋子”的题材。所有需要给克隆的类都得以ICloneable接口,包括Shoe类。

System.IClonealbe只生一个方法定义:Clone()

public object Clone()

{

}

俺们应当于Shoe类中这样实现:

public class Shoe : ICloneable

{

public string Color;

#region ICloneable Members

public object Clone()

{

Shoe newShoe = new Shoe();

newShoe.Color = Color.Clone() as string;

return newShoe;

}

#endregion

}

当方法Clone()中,我们创建了一个初的Shoe对象,克隆了有援类型,并拷贝了拥有值类型,然后回来了此新对象。你或注意到了string类已经落实了ICloneable接口,所以我们可一直调用Color.Clone()方法。因为Clone()方法返回的凡目标的援,所以我们得以设置鞋的颜料前重构这个引用。

继,在我们的CopyDude()方法吃我们得克隆鞋子要非拷贝它们:

public Dude CopyDude()

{

Dude newPerson = new Dude();

newPerson.Name = Name;

newPerson.LeftShoe = LeftShoe.Clone() as Shoe;

newPerson.RightShoe = RightShoe.Clone() as Shoe;

return newPerson;

}

如今,当我们实施Main()函数时:

public static void Main()

{

Dude Bill = new Dude();

Bill.Name = “Bill”;

Bill.LeftShoe = new Shoe();

Bill.RightShoe = new Shoe();

Bill.LeftShoe.Color = Bill.RightShoe.Color = “Blue”;

Dude Ted = Bill.CopyDude();

Ted.Name = “Ted”;

Ted.LeftShoe.Color = Ted.RightShoe.Color = “Red”;

Console.WriteLine(Bill.ToString());

Console.WriteLine(Ted.ToString());

}

咱俩得的凡:

Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left
foot
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left
foot

这虽是我们怀念如果的。

图片 3

以通常状态下,我们该”克隆”引用类型,”拷贝”值类型。(这样,在公调试以上介绍的情屡遭之题目时,会减小你购买来控制头痛的阿司匹林的药量)
于烦减少的利害下,我们得再次进一步地运用Dude类来落实IClonealbe,而休是行使CopyDude()方法。

public class Dude: ICloneable

{

public string Name;

public Shoe RightShoe;

public Shoe LeftShoe;

public override string ToString()

{

return (Name + ” : Dude!, I have a ” + RightShoe.Color +

” shoe on my right foot, and a ” +

LeftShoe.Color + ” on my left foot.”);

}

#region ICloneable Members

public object Clone()

{

Dude newPerson = new Dude();

newPerson.Name = Name.Clone() as string;

newPerson.LeftShoe = LeftShoe.Clone() as Shoe;

newPerson.RightShoe = RightShoe.Clone() as Shoe;

return newPerson;

}

#endregion

}

下一场我们将Main()方法吃的Dude.CopyDude()方法改吗Dude.Clone():

public static void Main()

{

Dude Bill = new Dude();

Bill.Name = “Bill”;

Bill.LeftShoe = new Shoe();

Bill.RightShoe = new Shoe();

Bill.LeftShoe.Color = Bill.RightShoe.Color = “Blue”;

Dude Ted = Bill.Clone() as Dude;

Ted.Name = “Ted”;

Ted.LeftShoe.Color = Ted.RightShoe.Color = “Red”;

Console.WriteLine(Bill.ToString());

Console.WriteLine(Ted.ToString());
}

末了之结果是:

Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left
foot.
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left
foot.

非常好!

比较好玩的凡要留意呢System.String类分配的操作符(“=”号),它事实上是以string型对象开展克隆,所以您不要担心会发出引用拷贝。尽管如此你要得注意一下内存的膨胀。
一经您重新看一下面前的那些图,会发觉string型应该是引用类型,所以其应该是一个指南针(这个指针指向堆中的其余一个对象),但是为了便于起见,我于图中将string型表示为值类型(实际上应该是一个指南针),因为通过”=”号还为赋值的string型对象实际是深受电动克隆过后底。

总结一下:

便,如果我们打算用我们的靶子用于拷贝,那么我们的切近应该实现IClonealbe借口,这样能使援类型仿效值类型的行。从中可以看到,搞明白我们所使用的变量的品种是殊重要的,因为于值类型和援类型的靶子在内存中的分红是生分之。

以产局部内容遭,会看出我们是哪些来压缩代码在内存中之”脚印”的,将会谈及期待已久的污物回收器(Garbage
Collection)。

To be continued…

.NET中栈和堆的于-四

到底翻了了季篇,本来每次都是周末发的,可惜上星期小事没忙过来,所以今天中午被补偿及来。不了解就套文章还能无可知延续了,因为作者也无非写到了季首,连他都非晓得第五首什么时候起得来…
初稿出处 http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp\_memory\_401282006141834PM/csharp\_memory\_4.aspx
得参照该系列文章的前头有情:Part
I
Part
II
Part
III

尽管在.NET framework下我们并不需要担心内存管理及污染源回收(Garbage
Collection),但是咱要当了解它们,以优化我们的应用程序。同时,还得具有一些基础之内存管理工作机制的学问,这样会促进讲我们司空见惯程序编制中之变量的一言一行。在本文中我们以深刻明垃圾回收器,还有如何运用静态类成员来如果我们的应用程序更迅捷。

* 更小的步子 == 更敏捷的分配

为还好地掌握为什么还小之足迹会再次速,这需要我们对.NET的内存分配与废品回收专研得更要命有。

* 图解:

吃咱来细看看GC。如果我们要背”清除垃圾”,那么我们用拟定一个速之方案。很显著,我们需要控制如何东西是垃圾要什么不是。
为操纵哪些是内需保留的,我们第一要有的事物还未是垃圾堆(墙角里堆在的初报纸,阁楼里珍藏的杂质,壁橱里之拥有东西,等等)。假设以我们的生存当中来三三两两位朋友:Joseph
Ivan Thomas(JIT)和Cindy Lorraine
Richmond(CLR)。Joe和Cindy知道她于以啊,而且受了咱们一样张列表说明了俺们得用来什么。我们以启列表称之为”根”列表,因为咱们用她之所以作起始点。我们要保留一摆主列表来记录有我们家的必需品。任何能够如必备物品正常干活或者用的事物吗用受填补加到列表中来(如果我们只要看电视,那么就未能够丢弃掉遥控器,所以遥控器将吃填补加至列表。如果我们要采用微机,那么键盘和显示器就得长到列表)。

即时就算是GC如何保存我们的品的,它由当下编译器(JIT)和通用语言运行时(CLR)中取”根”对象引用的列表,然后递归地摸来任何对象引用来树平等摆放我们需要保留的物品的图。

根包括:

*
全局/静态指针。为了使我们的对象非为垃圾回收掉的一样种方法是用她的援保存于静态变量中。
* 栈上之指针。我们不思量抛弃应用程序中得实施之线程里之事物。
*
CPU寄存器指针。托管堆着如何给CPU寄存器直接指向的内存地址上的事物必须得保留。

图片 4

于以上图片被,托管堆着之靶子1、3、5还为彻底所引用,其中1及5时直叫引述,而3时以递归查找时吃发现的。像咱之前的假设一样,对象1凡咱的电视机,对象3是咱们的遥控器。在有目标被递归查找出以后我们以进入下一样步–压缩。

* 压缩

咱们今天既绘制有哪些是咱需要保留的靶子,那么我们就能通过移动”保留对象”来针对托管堆进行整。

图片 5

侥幸的是,在我们的屋子里从未必要为了放入别的东西只要错过清理空间。因为对象2业已不再要了,所以GC会将目标3移下来,同时修补它对对象1的指针。

图片 6

接下来,GC将对象5为朝着下转移,

图片 7

今日有着的物都叫清理彻底了,我们只是需要写一布置就是签贴到压缩后的积上,让Claire(指CLR)知道当何方放入新的靶子就实行了。

图片 8

了解GC的实质会为我们理解对象的位移是老大吃力的。可以观看,假如我们会抽用活动的物品大小是不行有义之,通过再少之正片动作能够如我们提升所有GC的处理性能。

* 托管堆之外是哪些的气象也?

当负责垃圾回收的人口,有一个便于出现入的问题是以扫屋子时怎样处理车里的物,当我们打扫卫生时,我们要用富有物品清理彻底。那女人的台灯和车里的电池组怎么处置?

每当片动静下,GC需要实行代码来清理非托管资源(如文件,数据库连接,网络连接等),一种植或的艺术是经finalizer来展开处理。

图片 9class Sample
图片 10
图片 11图片 12图片 13{
图片 14
图片 15 ~Sample()
图片 16
图片 17图片 18
图片 19{
图片 20
图片 21 // FINALIZER: CLEAN UP
HERE
图片 22
图片 23 }
图片 24
图片 25}
图片 26

于目标创建期间,所有带有finalizer的靶子都用被补充加到一个finalizer队列中。对象1、4、5都产生finalizer,且都早已在finalizer队列中路。让咱们来瞧当目标2暨4于应用程序中不再给引用,且系正准备展开垃圾回收时会有数什么。

图片 27

对象2会像普通状态下那样让垃圾回收器回收,但是当我们处理目标4时常,GC发现其有于finalizer队列中,那么GC就无会见回收对象4之内存空间,而是用对象4底finalizer移到一个称作”freachable”的特有班中。

图片 28

发一个特意的线程来推行freachable队列中的起,对象4之finalizer一旦被该线程所处理,就拿由freachable队列中让移除,然后对象4就是待被回收。

图片 29

因此对象4以现有到下一致轮子的废品回收。

是因为在看似吃补充加一个finalizer会增加GC的工作量,这种工作是不行值钱的,而且会潜移默化垃圾回收的性及我们的顺序。最好不过于您肯定要finalizer时才用其。

于清理非托管资源时出同等种更好之法:在显式地关连接时,使用IDisposalbe接口来代替finalizer进行清理工作会晤还好把。

* IDisposable

贯彻IDisposable接口的类似需要实施Dispose()方法来开清理工作(这个法是IDisposable接口中唯一的签字)。因此如果我们下如下的含finalizer的ResourceUser类:

图片 30public class ResourceUser
图片 31
图片 32图片 33图片 34{
图片 35
图片 36 ~ResourceUser() // THIS IS
A FINALIZER
图片 37
图片 38图片 39
图片 40{
图片 41
图片 42 // DO CLEANUP HERE
图片 43
图片 44 }
图片 45
图片 46}
图片 47

咱们得利用IDisposable来坐重新好之点子实现平等的效应:

图片 48public class ResourceUser :
IDisposable
图片 49
图片 50图片 51图片 52{
图片 53
图片 54图片 55
IDisposable Members#region IDisposable Members
图片 56
图片 57 public void Dispose()
图片 58
图片 59图片 60
图片 61{
图片 62
图片 63 // CLEAN UP HERE!!!
图片 64
图片 65 }
图片 66
图片 67 #endregion
图片 68
图片 69}
图片 70

IDisposable被并入以了using块当中。在using()方法中扬言的靶子在using块的结尾处将调用Dispose()方法,using块之外该对象将不再给引述,因为它们已深受当是需要进行垃圾回收的目标了。

图片 71public static void
DoSomething()
图片 72
图片 73图片 74图片 75{
图片 76
图片 77ResourceUser rec = new
ResourceUser();
图片 78
图片 79using (rec)
图片 80
图片 81图片 82图片 83{
图片 84
图片 85 // DO SOMETHING
图片 86
图片 87} // DISPOSE CALLED HERE
图片 88
图片 89 // DON’T ACCESS rec HERE
图片 90
图片 91}
图片 92

自更爱好以对象声明放到using片被,因为如此可视化很强,而且rec对象在using块的作用域之外将不再有效。这种模式之写法更称IDisposable接口的初衷,但这并无是须的。

图片 93public static void
DoSomething()
图片 94
图片 95图片 96图片 97{
图片 98
图片 99using (ResourceUser rec =
new ResourceUser())
图片 100
图片 101图片 102图片 103{
图片 104
图片 105 // DO SOMETHING
图片 106
图片 107
图片 108
图片 109} // DISPOSE CALLED HERE
图片 110
图片 111}
图片 112

以类似中采用using()块来促成IDisposable接口,能够如我们在清理废品对象时无需写额外的代码来强制GC回收我们的对象。

* 静态方法

静态方法属于同一栽档次,而未是目标的实例,它同意创建能够给接近所共享的法门,且会达到”减肥”的效果,因为只有静态方法的指针(8
bytes)在内存当中走。静态方法实体仅以应用程序生命周期的头为一次性加载,而休是以咱们的类实例中生成。当然,方法尤其怪那么将那当作静态就越是高效。假如我们的艺术好有些(小于8
bytes),那么用该看作静态方法反而会影响属性,因为这时候指针比它对的法子所占的空间还百般来。

随之来探望例子…

俺们的近乎中产生一个公共的法门SayHello():

图片 113class Dude
图片 114
图片 115图片 116图片 117{
图片 118
图片 119 private string _Name =
“Don”;
图片 120
图片 121
图片 122
图片 123 public void SayHello()
图片 124
图片 125图片 126
图片 127{
图片 128
图片 129
Console.WriteLine(this._Name + ” says Hello”);
图片 130
图片 131 }
图片 132
图片 133}
图片 134

于每一个Dude类实例中SayHello()方法还见面占有内存空间。

图片 135

如出一辙种植更高效之计是采取静态方法,这样我们只是需要在内存中放置唯一的SayHello()方法,而无在多少只Dude类实例。因为静态成员不是实例成员,我们不能够采用this指针来进行方式的援。

图片 136class Dude
图片 137
图片 138图片 139图片 140{
图片 141
图片 142 private string _Name =
“Don”;
图片 143
图片 144
图片 145
图片 146 public static void
SayHello(string pName)
图片 147
图片 148图片 149
图片 150{
图片 151
图片 152 Console.WriteLine(pName +
” says Hello”);
图片 153
图片 154 }
图片 155
图片 156}
图片 157

图片 158

求小心我们在传递变量时栈上发了些什么(可以参见<第二有的>)。我们得经过例子的探访是不是用动用静态方法来提升性能。例如,一个静态方法需要广大参数而且从不啊复杂的逻辑,那么以利用静态方法时我们兴许会见跌性能。

* 静态变量:注意了!

于静态变量,有三三两两宗工作我们得注意。假如我们的类吃发出一个静态方法用于返回一个唯一值,而下的贯彻会晤促成bug:

图片 159class Counter
图片 160
图片 161图片 162图片 163{
图片 164
图片 165 private static int
s_Number = 0;
图片 166
图片 167 public static int
GetNextNumber()
图片 168
图片 169图片 170
图片 171{
图片 172
图片 173 int newNumber =
s_Number;
图片 174
图片 175 // DO SOME STUFF
图片 176
图片 177 s_Number = newNumber +
1;
图片 178
图片 179 return newNumber;
图片 180
图片 181 }
图片 182
图片 183}
图片 184

一经发生少数单线程同时调用GetNextNumber()方法,而且她在s_Number的价长前都也newNumber分配了扳平之值,那么它以回到同样的结果!

咱俩要出示地啊法吃的静态变量锁住读/写内存的操作,以保险平等时刻才来一个线程能够实施其。线程管理是一个特别可怜的主题,而且有无数门道得以缓解线程同步的问题。使用lock关键字能让代码块当相同时刻才能为一个线程访问。一栽好的习惯是,你应有尽可能沿较短的代码,因为在程序执行lock代码块常享有线程都要登待队列,这是雅低效的。

图片 185class Counter
图片 186
图片 187图片 188图片 189{
图片 190
图片 191 private static int
s_Number = 0;
图片 192
图片 193 public static int
GetNextNumber()
图片 194
图片 195图片 196
图片 197{
图片 198
图片 199 lock (typeof(Counter))
图片 200
图片 201图片 202
图片 203{
图片 204
图片 205 int newNumber =
s_Number;
图片 206
图片 207 // DO SOME STUFF
图片 208
图片 209 newNumber += 1;
图片 210
图片 211 s_Number = newNumber;
图片 212
图片 213 return newNumber;
图片 214
图片 215 }
图片 216
图片 217 }
图片 218
图片 219}
图片 220

* 静态变量:再次注意了!

静态变量引用得小心的外一样桩事情是:记住,被”root”引用的事物是勿见面吃GC清理掉的。我赶上了之一个太累人的例证:

图片 221class Olympics
图片 222
图片 223图片 224图片 225{
图片 226
图片 227 public static
Collection<Runner> TryoutRunners;
图片 228
图片 229}
图片 230
图片 231
图片 232class Runner
图片 233
图片 234图片 235图片 236{
图片 237
图片 238 private string
_fileName;
图片 239
图片 240 private FileStream
_fStream;
图片 241
图片 242 public void GetStats()
图片 243
图片 244图片 245
图片 246{
图片 247
图片 248 FileInfo fInfo = new
FileInfo(_fileName);
图片 249
图片 250 _fStream =
_fileName.OpenRead();
图片 251
图片 252 }
图片 253
图片 254}
图片 255

由于Runner集合在Olympics类中是静态的,不仅汇聚中的对象非会见于GC释放(它们都直接吃彻底所引用),而且若恐怕注意到了,每次执行GetStats()方法时还见面吗特别文件开放一个文书流,因为其从未让关所以也无会见为GC释放,这个代码用会晤为系统造成非常特别的天灾人祸。假如我们发出100000只选手来出席奥林匹克,那么会由于太多不可回收的目标要难以释放内存。天啦,多差劲的属性呀!

* Singleton

发出雷同种植方法可以保一个类的实例在内存中尽保唯一,我们可利用Gof中的Singleton模式。(Gof:Gang
of
Four,一部大有代表性的设计模式书籍的作者别称,归纳了23栽常用之设计模式)

图片 256public class Earth
图片 257
图片 258图片 259图片 260{
图片 261
图片 262 private static Earth
_instance = new Earth();
图片 263
图片 264图片 265
private Earth() 图片 266{ }
图片 267
图片 268图片 269
public static Earth GetInstance()
图片 270{ return _instance; }
图片 271
图片 272}
图片 273

我们的Earth类有一个个体构造器,所以Earth类能够实施其的构造器来创造一个Earth实例。我们出一个Earth类的静态实例,还有一个静态方法来获取这个实例。这种奇异之兑现是线程安全之,因为CLR保证了静态变量的缔造是线程安全之。这是自己觉得在C#惨遭落实singleton模式极其明智之方法。

* .NET Framework 2.0中之静态类

以.NET 2.0
Framework中我们发同等种静态类,此类中之持有成员都是静态的。这被特性对工具类是杀实用之,而且能节约内存空间,因为此类只设有让内存中的有地方,不能够以任何情形下被实例化。

* 总结一下…

总的来说,我们会提升GC表现的艺术发出:

1.
清理工作。不要吃资源一直打开!尽可能地确保关闭所有打开的总是,清除所有非托管的资源。当使用非托管对象时,初始化工作尽量完些,清理工作如尽可能及时点。

2.
永不过分地引用。需要经常才用引用对象,记住,如果你的靶子是活动正在的,所有为她引用的目标还无见面让垃圾回收。当我们想清理一些类似所引用的事物,可以通过以这些引用设置也null来移除它们。我爱以的同等栽艺术是将未运的援指向一个轻量级的NullObject来避免生出null引用的大。在GC进行垃圾回收时,更不见的援将回落映射处理的压力。

3.
不翼而飞使finalizer。Finalizer在垃圾堆回收时是充分贵的资源,我们应该只在必要常常以。如果我们应用IDisposable来替代finalizer会更高速些,因为咱们的对象会一直为GC回收而非是在第二坏回收时展开。

4.
尽量保持对象和它的旁对象在联合。GC在复制大块内存数据来放置一起时是深轻之,而复制堆中的零碎是殊棘手的,所以当我们声明一个分包多旁对象的靶子时,我们应有以初始化时尽可能让她们在一块。

  1. 末了,使用静态方法来维持对象的便利也是实用之。

产一致次等,我们用尤为尖锐GC的处理过程,看看当您的程序执行时GC是哪些察觉题目并消除其的。

To be long long continued…

相关文章