前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >我所理解的Remoting(2):远程对象生命周期的管理[上篇]

我所理解的Remoting(2):远程对象生命周期的管理[上篇]

作者头像
蒋金楠
发布2018-01-16 17:07:39
6390
发布2018-01-16 17:07:39
举报
文章被收录于专栏:大内老A大内老A

1.CLR的垃圾回收机制

在.NET中提到对象的生命周期,我们会不由自主地想到CLR的垃圾回收。在运行一个.NET程序过程中,我们通过某种方式,比如通过new操作符,通过反序列化,通过反射机制,创建一个对象,CLR在为这个对象在托管堆中开辟一块内存空间。随着程序的运行,创建的对象越来越多,托管堆中的可用的内存越来越少,必须有一种机制来判断被分配在托管堆中的对象那些已经不被使用,以及进行对这些对象占用的内存进行回收。这种机制被称为CLR自动内存管理,也就是我们常说的垃圾回收。为了说清楚远程对象的生命周期管理,我们 得首先了解本地对象的生命周期。

首先我们来说说CLR如何判断分配在托管堆中的对象那些是可以被垃圾回收的。我想我们应该可以想象得到,在程序运行的某个时刻,以下一些对象是会在后续的运行中会时候使用到的:一个类的静态字段,一个全局变量,调用方法堆栈中存储的方法参数和创建的局部变量,CPU 寄存器。我们一般把这些对象称为根(root),所有的根和被它直接引用或间接引用的对象,我们都认为是不应该被回收的。在CLR的内部维持着一个特殊的数据结构,这个表的每个条目对应一个跟,当CLR加载的时候,该表被创建。随着程序的不断运行,新的根会被加进来,已经不是跟的会被删除掉,所用这个表可以看作根的实时的反应。当垃圾回收器进行工作的时候(一般是第0代对象所对应的托管堆充满的时候,当然你也可以手动地调用GC.Collect方法启动垃圾回收。),它首先在托管堆上扫描,如果发现该的内存快所对应的对象是一个根,它会在该对象同步快索引(synchronous block index)字段做一个特殊的标记,表明它当前还不可以被垃圾回收,接着他会一递归的方式标记所有 被它直接或者间接引用的对象。所以当扫描完毕之后,那些被标记的对象就是在当前不可以被当成垃圾回收的对象,这些对象一般称为可达对象(reachable object,因为可以通过根来找到),反之,除此以外的对象则就是垃圾对象了。

标记可达对象只是垃圾回收的第一步,第二步才是对这些未被标记的垃圾对象进行回收。在开始之前我们必须能够分别两种不同的对象,一种称为可终结(Finalizable)对象和非可终结对象。我们知道,在很多情况下,我们的对象会引用一些非托管的资源,比如一个文件的句柄,一个数据库连结,或者一个Socket连结等等。在我们回收这些对象的时候,如果没有这些非托管的资源进行相应的终结操作的话,很有可能造成内存的泄漏。在.NET中,我们通常把这些终结操作写在一个特殊的方法中,叫做Finalize。在C#中我们一般定于在~ClassName()的形式,并且沿用C++ 的说法,称它为析构函数(我不推荐这么称呼,Finalize方法和C++的析构函数是不同的)。如果你有兴趣查看C#编译之后生成的IL代码,你会发现定义成~ClassName()的方法的名称就是Finalize。我们把相应的类定义了Finalize方法的对象叫做可终结的对象。说到可终结对象,这里我们又需要引入一个新的垃圾回收器维护的数据结构,这是的链表,用于保存未被回收的可终结对象,一般称为终结链表。

接下来我们看看垃圾回收器如何进行垃圾回收的。垃圾回收器开始再一次扫描托管堆,对于在第一步作了标记的对象,不予理睬。对于未作标记的对象,首先判断是否是可终结对象(看看在终结链表中是否有相应的对象),如果不是,直接回收掉。否则垃圾回收器还要先判断是否已经进行了终结操作,如果没有则会把它从终结链表中移除,把对象放入另一个称为终结可达对列(freachable queue——f代表finalizable,注意这里又引进了一个新的数据结构)。如果已经进行了终结操作,则直接进行回收就好了。

对于放入终结可达对列对象,我们必须在对它进行垃圾回收之前收前进行终结操作。从广义来讲终结可达对列中的对象也是一个根,所以被放入终结可达对列中的对象是不应该被垃圾回收的,由于终结操用涉及到线程同步的问题,所有的终结操作都在一个具有较高优先级的线程上进行。这个线程在终结可达对列为空的时候处于休眠的状态,一旦垃圾回收器把一个可终结对象放入这个终结可达对列的时候,这个特殊的线程立即被唤醒,调用该对象的Finalize方法并把它从终结可达对列中移除。等再一次进行回收的时候,对于这些经过终结操作对象已经成为垃圾对象——不会有任何的根,包括终结可达对列引用它,这个时候垃圾回收器可以对它进行回收了。

从垃圾的整个过程来看,如果我们重写了Finalize方法使之成为一个可终结类型,这种对象实际上要经过两次垃圾回收才会被真正地回收调——其中一次放入终结可达对列,另一次才真正被回收调。所以,我们在定义某个类型的时候,如果没有必要重写Finalize方法就千万不要重写它,不然会加重内存的压力,降低应用的性能。

2.基于租约(Lease)的生命周期管理机制

在前面我们简单地讲述了CLR垃圾回收机制。按照这种机制,如果我们要阻止一个对象被垃圾回收,我们必须让它被某个根直接或间接引用,而这个引用它的对象一般应该和该对象处于同一个Application Domain之中。所以这种机制不适合我们分布式环境。在一个分布式应用来说,服务器段对象通过Marshaling从Sever端的Application Domain传到Client所在的Application Domain。无论采用哪种Marshal方式,By Value 或者By Reference,Marshal本身是不会为远程对象在Server端的Application Domain创建一个引用的。如果这样的话,对于垃圾回收器来说,远程对象便成了一个垃圾对象,在进行垃圾回收的时候,必然会被回收掉。如果Client端调用一个已经被回收的远程对象,对于Client Activated Object,会抛出异常,如果对于Singleton模式的Server Activated Object,Remoting Framework会重新创建一个新的对象,原有的状态将不复存在。所以我们必须有一种机制来控制远程对象的生命周期。这就是我们现在讲的基于租约(Lease)的生命周期管理。

在正式讲述之前,我首先我讲一个现实生活中的一个困难不是很恰当的例子,一个租房的例子。

去年9月份,我从苏州来到上海,通过中介租了一间房子,很巧的是这个中介就是我的房东,并且我是和房东合租的。当时觉得不是太满意,所以签合同的时候只签了半年。在租期还没有到期的时候,我有权向房东提出续租,租期可以使半年,也可以使一年。如果到期了,我没有提出续租,房东肯定会主动和我联系,询问我时候有续租的打算,如果我提出续租,考虑到我们房东和和睦相处的关系,我们可以在不需要签订新的合同的情况下让我续租。否则我走人,我的房间被转租出去。如果房东在找我的时候,可能我出差了,手机又换号了,联系不到我,这时候,他肯定会打联系我的女朋友,他们也很熟,如果我的女朋友说要续租,房东便会在没有获得我答复的情况下,给我续租。但是现在租期已经到期了,我也没有提出续租,房东也没有叫我续租,但是我还是每个月给他交房东,虽然合同已经在法律的意义上失效了,但是我和清楚,我交了下个月的房租,我的房间到下个月底使用权归我。这就是我租房的故事(呵呵)。大家注意这样故事中的几个实体,合同,房东兼中介,房间,我(合同上的承租者),我女朋友。

现在我们再来讲Remoting关于Lease的对象生命周期的管理机制。当远程对象被激活和Marshal的时候,处于Server端Application Domain的Lease Manager会为该远程对象创建一个Lease。就相当于中介和我签了一份租房合同,中介相当于Lease Manager,我(承租者)相当于客户端,而房间就是这个远程对象,Lease则代表我们签订的租房合同。就像合同会写明租期一样,Lease也会有一个初始的时间(InitialLeaseTime)代表远程对象的初始生命周期。就像我可以在租期到期之前可以自动提出延长租期一样,Client可以通过这个Lease来延长对应远程对象的生命周期。不过和租房的例子不同的是,Server端也可以具有相同的权利。

就像我可以通过交房租来延长一个月的租期一样,远程对象可以通过来自Client端的调用来延长这个Lease,这个时间由属性RenewOnCallTime来表示。不过有一点值得注意的是,就像我在租期到了的那个月之前交房租这个行为不会延长租期(始终是6个月),只有我在第6个月月底交房租才会把实际的租期延长到7月个。在Remoting中,只有在RenewOnCallTime大于Lease剩下的时间的时候,这个RenewOnCallTime才会起作用。

就像我可以让房东在我不在的时候,找我的女朋友来代表我一样,在Remoting中,Client可注册一个Sponsor,这个Sponsor有权代表Client延长租房期限,当然Client有权利取消这个注册的Sponsor,就像有一天我和女朋友分手了,她就没有这样的权利了。随着时间的推移,当Lease的过期了,Lease Manager会首先通过远程调用(可以把这种情况看成一种Callback),从Client获得Client为相应远程对象注册的Sponsor,找到了之后,通过这个Sponsor设置的时间来延长远程对象的生命周期。但是,我们已经说了,Lease Manager获得Sponsor是一种远程调用,可能他们处在不同的Application Domain,不同的Process,不同的Machine,甚至处于Internet的两端。这个调用是否成功和调用的时间是不确定的,所以这里必须给定一个特定的时间来规定,在某一段限定的时段内,如果不能获得相应的Sponsor则认为该Sponsor不可得。否则始终这个调用下去也不是个事儿。就像房东在房租到期一个月之内还找不到我和我女朋友,关系再好也必须把房间转租出去了。对于Lease来说,这个时间通过SponsorshopTimeout属性表示。

这里还有一个重要的时间,那就是Lease Manager每个多少时间扫描Lease——LeaseManagerPollTime。

上面的所有的时间都是可以定制的,我们现在看看,如何定制这些时间。

1. 通过编程的方式:通过设置System.Runtime.Remoting.Lifetime. LifetimeServices静态属性。

LifetimeServices.LeaseManagerPollTime = TimeSpan.FromMinutes(2);
LifetimeServices.LeaseTime = TimeSpan.FromMinutes(2);
LifetimeServices.RenewOnCallTime = TimeSpan.FromMinutes(2);
LifetimeServices.SponsorshipTimeout = TimeSpan.FromMinutes(2);

2. 通过Configuration的方式

<lifetime  leaseTime="7M"  sponsorshipTimeout="7M"  renewOnCallTime="7M" 
leaseManagerPollTime="7S" />

3. 定制单个MarshalByRefObject 对象的Lease 时间:Override 继承自MarshalByRefObject 的InitializeLifetimeService方法。

public override object InitializeLifetimeService()
        {
            ILease lease = (ILease)base.InitializeLifetimeService();
            if (lease.CurrentState == LeaseState.Initial)
            {
                lease.InitialLeaseTime = TimeSpan.FromSeconds(1);
                lease.RenewOnCallTime = TimeSpan.FromSeconds(1);
                lease.SponsorshipTimeout = TimeSpan.FromSeconds(1);
            }
            return lease;           
        }

通过上面的讲述,我的应该对Remoting对象生命基于Lease和Sponsorship的生命周期的管理有一个感性的认识。实际上Sponsorship远非这么简单,对Sponsorship的深入探讨,有兴趣话可以关注:[原创]我所理解的Remoting (2) :远程对象的生命周期管理-Part II . Reference:Jeffery Richiter 《CLR via C#》

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2007-03-22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档