马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
我有个同学,他是搞net网页编程的,他给我说“net网页编程不是效率低,而是速度慢。”,我不是搞net网页编程的,我实在想不透这句话的含义,难道执行速度不就是效率低吗?难道执行速度慢还成效率高了?系列文章目次索引:《你必需晓得的.NET》
本文将先容以下内容:
接上回[第十八回:对象创立委曲(上)],持续对对象创立话题的会商>>>
援用范例的实例分派于托管堆上,而线程栈倒是对象性命周期入手下手的中央。对32位处置器来讲,使用程序完成历程初始化后,CLR将在历程的可用地点空间上分派一块保存的地点空间,它是历程(每一个历程可以使用4GB)中可用地点空间上的一块内存地区,但其实不对应于任何物理内存,这块地点空间便是托管堆。
托管堆又依据存储信息的分歧分别为多个地区,个中最主要的是渣滓接纳堆(GCHeap)和加载堆(LoaderHeap),GCHeap用于存储对象实例,受GC办理;LoaderHeap又分为High-FrequencyHeap、Low-FrequencyHeap和StubHeap,分歧的堆上又存储分歧的信息。LoaderHeap最主要的信息就是元数据相干的信息,也就是Type对象,每一个Type在LoaderHeap上表现为一个MethodTable(办法表),而MethodTable中则纪录了存储的元数据信息,比方基范例、静态字段、完成的接口、一切的办法等等。LoaderHeap不受GC把持,其性命周期为从创立到AppDomain卸载。
在进进实践的内存分派剖析之前,有需要对几个基础观点做以交卸,以便更好的在接上去的剖析中睁开会商。
·TypeHandle,范例句柄,指向对应实例的办法表,每一个对象创立时都包括该附加成员,而且占用4个字节的内存空间。我们晓得,每一个范例都对应于一个办法表,办法表创立于编译时,次要包括了范例的特性信息、完成的接口数量、办法表的slot数量等。
·SyncBlockIndex,用于线程同步,每一个对象创立时也包括该附加成员,它指向一块被称为SynchronizationBlock的内存块,用于办理对象同步,一样占用4个字节的内存空间。
·NextObjPtr,由托管堆保护的一个指针,用于标识下一个新建对象分派时在托管堆中所处的地位。CLR初始化时,NextObjPtr位于托管堆的基地点。
因而,我们对援用范例分派历程应当有个基础的懂得,因为本篇示例中FileStream范例的承继干系绝对庞大,在此本文完成一个绝对复杂的范例来做申明:
将上述实例的实行历程,反编译为IL言语可知:new关头字被编译为newobj指令来完成对象创立事情,进而挪用范例的机关器来完成其初始化操纵,在此我们具体的形貌其实行的详细历程:
//@2007Anytao.com
//http://www.anytao.com
publicclassUserInfo
{
privateInt32age=-1;
privatecharlevel="A";
}
publicclassUser
{
privateInt32id;
privateUserInfouser;
}
publicclassVIPUser:User
{
publicboolisVip;
publicboolIsVipUser()
{
returnisVip;
}
publicstaticvoidMain()
{
VIPUseraUser;
aUser=newVIPUser();
aUser.isVip=true;
Console.WriteLine(aUser.IsVipUser());
}
}
·起首,将声明一个援用范例变量aUser:
VIPUseraUser;
它仅是一个援用(指针),保留在线程的仓库上,占用4Byte的内存空间,将用于保留VIPUser对象的无效地点,其实行历程恰是上文形貌的在线程栈上的分派历程。此时aUser未指向任何无效的实例,因而被自行初始化为null,试图对aUser的任何操纵将抛出NullReferenceException非常。
·接着,经由过程new操纵实行对象创立:
aUser=newVIPUser();
如上文所言,该操纵对应于实行newobj指令,其实行历程又可细分为以下几步:
(a)CLR依照其承继条理举行搜刮,盘算范例及其一切父类的字段,该搜刮将一向递回到System.Object范例,并前往字节总数,以本例而言范例VIPUser必要的字节总数为15Byte,详细盘算为:VIPUser范例自己字段isVip(bool型)为1Byte;父类User范例的字段id(Int32型)为4Byte,字段user保留了指向UserInfo型的援用,因而占4Byte,而同时还要为UserInfo分派6Byte字节的内存。
实例对象所占的字节总数还要加上对象附加成员所需的字节总数,个中附加成员包含TypeHandle和SyncBlockIndex,合计8字节(在32位CPU平台下)。因而,必要在托管堆上分派的字节总数为23字节,而堆上的内存块老是依照4Byte的倍数举行分派,因而本例中将分派24字节的地点空间。
(c)CLR在以后AppDomain对应的托管堆上搜刮,找到一个未利用的20字节的一连空间,并为其分派该内存地点。现实上,GC利用了十分高效的算法来满意该哀求,NextObjPtr指针只必要向前促进20个字节,并清零原NextObjPtr指针和以后NextObjPtr指针之间的字节,然后前往原NextObjPtr指针地点便可,该地点恰是新创立对象的托管堆地点,也就是aUser援用指向的实例地点。而此时的NextObjPtr仍指向下一个新建对象的地位。注重,栈的分派是向低地点扩大,而堆的分派是向洼地址扩大。
别的,实例字段的存储是有按次的,由上到下顺次分列,父类在前子类在后,具体的剖析请拜见[第十五回:承继实质论]。
在上述操纵时,假如试图分派所需空间而发明内存不敷时,GC将启动渣滓搜集操纵往返收渣滓对象所占的内存,我们将今后对此做具体的剖析。
·最初,挪用对象机关器,举行对象初始化操纵,完成创立历程。该机关历程,又可细分为以下几个环节:
(a)机关VIPUser范例的Type对象,次要包含静态字段、办法表、完成的接口等,并将其分派在上文提到托管堆的LoaderHeap上。
(b)初始化aUser的两个附加成员:TypeHandle和SyncBlockIndex。将TypeHandle指针指向LoaderHeap上的MethodTable,CLR将依据TypeHandle来定位详细的Type;将SyncBlockIndex指针指向SynchronizationBlock的内存块,用于在多线程情况下对实例对象的同步操纵。
(c)挪用VIPUser的机关器,举行实例字段的初始化。实例初始化时,会起首向上递回实行父类初始化,直到完成System.Object范例的初始化,然后再前往实行子类的初始化,直到实行VIPUser类为止。以本例而言,初始化历程为起首实行System.Object类,再实行User类,最初才是VIPUser类。终极,newobj分派的托管堆的内存地点,被传送给VIPUser的this参数,并将其援用传给栈上声明的aUser。
上述历程,基础完成了一个援用范例创立、内存分派和初始化的全部流程,但是该历程只能看做是一个简化的形貌,实践的实行历程加倍庞大,触及到一系列细化的历程和操纵。对象创立并初始化以后,内存的结构,能够暗示为:
<br>
由上文的剖析可知,在托管堆中增添新的实例对象,只是将NextObjPtr指针增添必定的数值,再次新增的对象将分派在以后NextObjPtr指向的内存空间,因而在托管仓库中,一连分派的对象在内存中必定是一连的,这类分派机制十分高效。
有了对象创立的基础流程观点,上面的几个成绩经常引发人人的思索,在此本文一并做以探究:
·值范例中的援用范例字段和援用范例中的值范例字段,其分派情形又是怎样?
这一思索实际上是一个成绩的两个方面:关于值范例嵌套援用范例的情形,援用范例变量作为值范例的成员变量,在仓库上保留该成员的援用,而实践的援用范例仍旧保留在GC堆上;关于援用范例嵌套值范例的情形,则该值范例字段将作为援用范例实例的一部分保留在GC堆上。在[第八回:咀嚼范例---值范例与援用范例(上)-内存有理]一文对这类嵌套布局,有较具体的剖析。关于值范例,你只需记住它老是分派在声明它的中央。
·办法保留在LoaderHeap的MethodTable中,那末办法挪用时又是怎样的历程?
如上文所言,MethodTable中包括了范例的元数据信息,类在加载时会在LoaderHeap上创立这些信息,一个范例在内存中对应一份MethodTable,个中包括了一切的办法、静态字段和完成的接口信息等。对象实例的TypeHandle在实例创立时,将指向MethodTable入手下手地位的偏移处(默许偏移12Byte),经由过程对象实例挪用某个办法时,CLR依据TypeHandle能够找到对应的MethodTable,进而能够定位到详细的办法,再经由过程JITCompiler将IL指令编译为当地CPU指令,该指令将保留在一个静态内存中,然后在该内存地点上实行该办法,同时该CPU指令被保留起来用于下一次的实行。
在MethodTable中,包括一个MethodSlotTable,称为办法槽表,该表是一个基于办法完成的线性链表,并依照以下按次分列:承继的虚办法,引进的虚办法,实例办法和静态办法。办法表在创立时,将依照承继条理向上搜刮父类,直到System.Object范例,假如子类覆写了父类办法,则将会以子类办法掩盖父类虚办法。关于办法表的创立历程,能够参考[第十五回:承继实质论]中的形貌。
·静态字段的内存分派和开释,又有何分歧?
静态字段也保留在办法表中,位于办法表的槽数组后,其性命周期为从创立到AppDomain卸载。因而一个范例不管创立几个对象,其静态字段在内存中也只要一份。静态字段只能由静态机关函数举行初始化,静态机关函数确保在范例任何对象创立前,大概在任何静态字段或办法被援用前实行,其具体的实行按次请参考相干会商。
对象创立历程的懂得,是从底层打仗CLR运转机制的出口,也是熟悉.NET主动内存办理的关头。经由过程本文的具体叙述,关于对象的创立、内存分派、初始化历程和办法挪用等手艺城市创建一个绝对周全的了解,同时也分明的掌控了线程栈和托管堆的实行机制。
对象老是有生有灭,本文简述其生,这是个巨大的入手下手。
参考文献
(USA)JoeDuffy,Professinal.NETFramework2.0
(USA)DonBox,Essiential.NET
(MSDN)HanuKommalapatiandTomChristian,DrillInto.NETFrameworkInternalstoSeeHowtheCLRCreatesRuntimeObjects,http://msdn.microsoft.com/msdnmag/issues/05/05/JITCompiler/default.aspx
2007Anytao.com
来自:http://www.ckuyun.com/anytao/archive/2007/12/07/must_net_19.html
完全不一样的。.net其实我也说不太清,.net可以把他理解为跟J2EE相对的工具。c++主要做系统相关的开发你要学.net的话就应该学C#。(其实微软在.NET平台上也考虑了给C++留一个地位。 |