我查看了this和this,我有以下问题,看看我是否理解正确。 鉴于代码
using System;
namespace returnObject
{
class myObject
{
public int number { get; set; }
}
class Program
{
static void Main(string[] args)
{
myObject mainObj = make();
mainObj.number = 7;
}
public static myObject make()
{
myObject localObj = new myObject();
localObj.number = 4;
return localObj;
}
}
}
我希望localObj
在make方法结束时超出范围,因此主函数中obj.number
到7的设置失败。它没有。
我认为我说的是正确的:
localObj
是对象的引用localObj
在堆栈上创建localObj
超出了make方法末尾的范围。 localObj
引用的对象在堆上。 所以,我是否正确地认为,localObj
引用的对象通常会被make方法末尾的垃圾收集器标记为删除,但由于引用值已传递回{ {1}},该对象被引用,因此不符合删除条件吗?
此外,以这种方式创建对象是一种良好的做法吗?
答案 0 :(得分:14)
我是否正确地认为通常localObj所引用的对象在make方法的末尾被垃圾收集器标记为删除,但由于引用值已传递回mainObj,因此引用该对象并且因此没有资格删除?
这是一个非常复杂的问题,但它的形式只承认是或否答案。不要试图回答这个问题,而是让我将其分解为原点后的点。
localObj是对象的引用
更好:localObj是一个局部变量。局部变量是引用类型的存储位置。存储位置包含对对象的引用,或为null。
在堆栈上创建了localObj
正确,尽管这是一个实现细节。与变量关联的存储位置是从临时池分配的。作为实现细节,CLR使用调用堆栈作为临时池。它不需要;堆栈只是获得临时池的一种便宜,便捷的方式。
localObj在make方法结束时超出范围。
正确。变量的范围被定义为程序文本的区域,可以通过其非限定名称来使用它。此特定变量的范围是方法的整个主体。 (我还注意到在C#中传统的方法,属性和类型都是大写字母,这是你没有做过的。)
localObj引用的对象在堆上。
正确。作为实现细节,所有引用都是null,或者引用长期存储中的对象,也就是托管堆。 CLR的实现可以使用流分析来确定特定对象不会转义保存对其唯一引用的局部变量的生命周期,因此将其分配给临时池。实际上,在我所知的任何实现中都不会发生这种情况。
如果没有返回,则localObj引用的对象将被make方法末尾的垃圾收集器标记为删除
否即可。这是你的第一个重大错误印象。 GC不是确定性。它没有看到立即局部变量已超出范围,因此对象已被孤立。该对象继续存在,直到某个策略触发垃圾回收。即使这样,该对象也可能在之前的集合中幸存下来并被提升为后一代。后代的收集频率较低。所有你知道的是,对象将不再被标记为生存。请记住,mark-n-sweep垃圾收集器将对象标记为生存,而不是进行删除。
此外,在 make方法结束之前,GC 清理对象是完全合法的。在处理托管/非托管代码互操作时,这是导致错误的常见原因。请考虑以下情形:
void M()
{
FileThingy f = GetFileThingy();
MyUnmanagedLibrary.ConsumeFileThingy(f);
}
什么时候可以收藏?一旦GC确定没有托管代码再次消耗该引用。但是,在非托管代码获取其引用副本后的瞬间,任何托管代码永远都不会使用此引用,因此GC在其调用后立即在另一个线程上收集对象的权限范围内在托管代码运行之前调用。要解决此问题,您需要使用 KeepAlive 来保持对象的活动状态。
当局部变量超出范围时,您无法依赖GC回收存储。对象的生命周期很可能比变量的生命周期长,并且如果GC可以证明托管代码无法告诉它,则合法更短差。
由于引用值已传递回mainObj,因此引用该对象,因此不符合删除条件
正确。但是,再次假设mainObj例程没有使用传回来的对象。抖动在其权利范围内,可以注意到这一事实,并优化了未使用数据的遍历,从而使对象可以用于早期收集。
我在这里得到的是垃圾收集器应该被认为比管理内存更聪明。该物体将在它需要的时候消失,并且可能在它可能的时间之后消失,但可能比你想象的要早。不要担心,要学会热爱不确定性; GC正在为你管理事情并且做得很好。
答案 1 :(得分:3)
C#不像C或C ++那样工作;对象永远不会在堆栈上分配。
所有对象(引用类型)都存在于垃圾收集堆上;垃圾收集器将在不再引用它们之后的某个时间收集对象。
在不使用弱引用的情况下,基本上不可能在管理对象GC之后观察它。
答案 2 :(得分:3)
你不应该对.net框架中的内存管理过多考虑:
它已经被创建,并且它被称为 托管代码 ,主要是出于这个原因。
如果它的引用在不同的范围之间共享,那么这个框架足够聪明,可以创建一个长寿命的对象;如果仅在有限的范围内使用,则该框架是短生命对象。
堆栈,堆等是实现细节,你不关心它们,正如Eric Lippert在几篇博客文章中所说的那样:然后,如果你需要在一个地方创建一个对象(例如一个方法),在另一个地方使用,不要担心内存分配......就这样做。
答案 3 :(得分:1)
localObj
。 C#中的所有引用类型都是。每当使用new
关键字时,这意味着创建的对象将转向堆。这就是为什么即使从方法返回后对对象的引用仍然有效。该对象将一直存在,直到垃圾收集器来抓取它。
localObj
在堆栈中。那是正确的。但是,引用指向堆上的对象。您返回了该引用的副本,因此新引用仍指向堆上的同一对象。
答案 4 :(得分:1)
尽管localObj变量超出了堆栈的范围,但它引用了堆上的对象,并且您已将引用作为返回值传递,因此Main方法现在引用堆上的同一对象。
垃圾收集器不会清理对象,除非程序不再使用它。
如果将此场景与C ++进行比较,可以将localObj视为一个指针 - 尽管返回了指向它的指针,该对象仍然保持分配状态。注意:C#托管代码和引用与C / C ++不同,因此这只是记住类似行为的一种方式。