首页 新闻 会员 周边

对于CLR VIA C# 一书的几个迫切需要解决的疑问

2
悬赏园豆:10 [已解决问题] 解决于 2012-09-22 17:46

最近一直在看clr via c#这本书 在核心机制之垃圾回收这章中 遇到了一个特别纠结的问题:

书的463页一段话:“类型 中定义的任何静态字段被认为是一个根,除此之外,任何方法参数或局部变量也被认为是一个根 只有引用类型的变量才被 认为是根 值类型的变量永远不被认为是根”

这短话让我非常纠结 方法参数和局部变量也有可能是值类型啊 这不 互相矛盾了麽???

------------------------------------------

还一个疑问 在书的466页:”类型的静态字段永远是它引用的任何对象的根“

这句话怎么理解呢  静态字段如何来引用一个对象呢 其实我对于根的理解也比较模糊 恳求大牛们解答!

果果天涯的主页 果果天涯 | 初学一级 | 园豆:16
提问于:2012-09-18 21:22
< >
分享
最佳答案
2

"只有引用类型的变量才被 认为是根 值类型的变量永远不被认为是根" 是对之前的补充,当“方法参数和局部变量是值类型”的时候,它们不会被认为是根。以下的可能会帮助你理解什么是根:

(1)GC可能在2个时候发生,一个是newobj的时候,一个是显式GC的时候,无论如何,肯定是由某个方法的某句代码引起的(一句new或者一句GC.xxxx)。

(2)当GC发生的时候会对所有堆上的对象进行标记工作,根据标记的结果来判定哪些是有用的,哪些是没用的。由于对象之间的引用关系很复杂(甚至存在互相循环引用),因此标记工作一定得有起点,这些起点就是根。例如根是[a,b,c]3个对象(先姑且不管为什么是它们3个),先考察a对象,a引用了e,因此e也被标记为有用的,e引用了f,f也被标记了……最终这个“深度优先搜索”结束了。回到b做同样的事情,然后是c。最后堆上的对象被分成了2组,1组是“从根作为起点,可以到达的(本质是存在引用路径关系的)”,另外一组是无法到达的(因此就是垃圾,可以被收集了)。这就是根的意义。

(3)现在来讲为什么静态字段要被作为根。其实这个我个人觉得是很容易理解的,因为静态字段在整个应用程序中是随时都可以被访问的。假设有这么个类:

public class Foo
{
       public static Bar InstanceOfBar = new Bar();    
       pubilc void A() { //do something }
       public void B() { //do something }
}
Foo.InstanceOfbar引用了堆上的一个Bar对象,那么这个对象无论如何都是不能被回收(直到当前应用程序结束),因为Foo.InstanceOfBar可以在任何时候被访问到。而这里假设去掉static,改成实例字段,那么这个bar对象会在它所在的foo对象被当作垃圾处理之后被标记为垃圾(因为除了那个foo对象能带领GC标记到达它之外,它无法被任何对象“到达”)。
(4)参数和局部变量也很好理解。先说参数:参数是从调用者(外部)传过来的,从它顺藤摸瓜,很容易把它的上一层,上上层(一直到应用程序入口函数)的所有有关系的(有用的)对象都标记出来。局部变量是当前方法体新定义的,它们指向的堆上的对象在此时必然是有用的(并且对象引用的对象也是有用的)。这3者能确定所有有用的对象,甚至会有重复,例如:
private void M(Foo f)
{
     Bar b = f.Bar;
}

这里无论是以f为根(起点),或者以b为根,都可以得出“f.Bar这个对象是有用的"这个结论。因此这3者可以完美覆盖所有的有用的对象,剩余的必然是可以被GC的。

 
收获园豆:10
水牛刀刀 | 大侠五级 |园豆:6350 | 2012-09-18 22:28

首先非常感谢您的精彩回答!

对于“静态字段永远被认为是根 我想我已经明白了 因为静态字段在整个应用程序的生命周期内都一直存在 那么它所引用的对象一定不能被视为垃圾  ”   但是 为什么一定要是方法里面的局部引用变量才被认为是根呢? 方法外面的变量就不行麽  这个我不明白 一个方法调用结束以后 在方法内部定义的引用变量所指向的对象 会一直常驻托管堆而不释放麽?

果果天涯 | 园豆:16 (初学一级) | 2012-09-19 17:33

@果果天涯: “方法外的变量”是指什么?你给我举个例子,给出代码。

水牛刀刀 | 园豆:6350 (大侠五级) | 2012-09-19 17:52

@水牛刀刀: 

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Collections;
 6 using System.Text.RegularExpressions;
 7 
 8 namespace ConsoleApplication2
 9 {
10     class Program
11     {
12         List<string> list = new List<string>();
13         static void Main(string[] args)
14         {       
15             Console.ReadLine();
16         }
17         public void Test()
18         {
19  
20         }
21 
22     }
23 }

比如说上面的 list  变量 不是Test方法外面的变量麽?

我知道我可能对这些概念有严重的误差 恳请刀刀不吝赐教 非常感谢!

果果天涯 | 园豆:16 (初学一级) | 2012-09-20 09:51

@果果天涯: 说list是变量不准确,应当说“list是Program类的成员(字段)”。你调用Test方法时:

var p = new Program();
p.Test();

假设在Test()中引起了一次GC,此时this(就是执行Test方法的当前实例,也就是p)是根,因此this.list(就是p.list)被标记成有用的,不会被GC。所以,你所认为的“方法外的变量不会被GC”,本质上是由于这个“字段”所属的那个实例不会被GC。

水牛刀刀 | 园豆:6350 (大侠五级) | 2012-09-20 12:57

@水牛刀刀: 我我 我再仔仔细细考虑 虽然我心中还是很多疑问  也许我对于“方法”这个概念一开始都没弄清楚 我觉得依据您的说法 实例字段 是由于这个实例存在 因此此实例字段不会被回收 这个是自然  可是 一个方法调用结束以后  方法内部的局部变量所引用的对象 为什么不能被回收呢?  还是可以这么认为:垃圾回收器在遍历应用程序的根的时候 是“遍历那些正在使用中的方法的局部变量”  而那些方法调用已经结束的局部变量 不当作它是根? 可以这么理解麽?ps:非常感谢您前面的耐心解答

果果天涯 | 园豆:16 (初学一级) | 2012-09-20 18:40

@果果天涯: 方法调用结束之后局部变量在GC时肯定是被标记成垃圾的,书中说的”方法“显然是指当前正在执行的方法啊!

class Program
{
       static void Main(string args[])     //入口方法
        {
                var something = new Something();
                A();
                B();
         }      

       static void A()
       {
               var a = new ItemA();
               //do something
        }

       static void B()
        {
               var b = new ItemB();
               C();
               //do something
        }

       static void C()
       {
              //GC here
       }

}

假设在C中发生了一次GC,那么此时的根并不包括A方法中的变量。因为此时活跃的方法是Main -> B -> C,此时的根是b,something,还有main的参数args。如果你真的认真看过CLR via C#,那么应该知道一个方法执行完成后栈帧会展开(就跟执行这个方法前一样),A方法执行完成后,栈上已经没有任何A方法的痕迹了。

水牛刀刀 | 园豆:6350 (大侠五级) | 2012-09-20 20:26

@水牛刀刀: 非常感谢您的耐心解答!

果果天涯 | 园豆:16 (初学一级) | 2012-09-22 17:46
其他回答(1)
0

根说法好象是有点比喻的感觉

jason2013 | 园豆:1998 (小虾三级) | 2012-09-19 07:54
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册