转贴: 在使用SPSite对象时容易发生内存泄漏!造成内存泄漏的原因是没有正确地关闭SPSite对象
如题所说,在使用SPSite对象时容易发生内存泄漏!造成内存泄漏的原因是没有正确地关闭SPSite对象,请大家Review一下代码,及时修正!
项目中,对系统进行压力测试时,出现了大量的异常信息,类似如下面的:
Microsoft.SharePoint.SPException: Attempted to make calls on more than one thread in single threaded mode. (Exception from HRESULT: 0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD)) ---> System.Runtime.InteropServices.COMException (0x80010102): Attempted to make calls on more than one thread in single threaded mode. (Exception from HRESULT: 0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD))
at Microsoft.SharePoint.Library.SPRequestInternalClass.SetVar(String bstrUrl, String bstrName, String bstrValue)
at Microsoft.SharePoint.Library.SPRequest.SetVar(String bstrUrl, String bstrName, String bstrValue)
--- End of inner exception stack trace ---
at Microsoft.SharePoint.Library.SPRequest.SetVar(String bstrUrl, String bstrName, String bstrValue)
at Microsoft.SharePoint.SPListItemCollection.EnsureListItemsData()
at Microsoft.SharePoint.SPListItemCollection.Undirty()
at Microsoft.SharePoint.SPBaseCollection.System.Collections.IEnumerable.GetEnumerator()
at OA2.MossAccessBlock.Portal.NewsHelper.GetAreaNewsDom(String subType, String area, Int32 pictureNum, Int32 newsNum) in D:\OA2_BuildVersion\MossAccessBlock\Portal\NewsHelper.cs:line 191
上面的异常的意思是说:在单线程模式下尝试进行多线程呼叫。在压力测试的过程中,出现了诸如“服务器内存不足”、“连接不到数据库服务器”、“找不到网站”等一些在单用户(用户访问量较小的情况下)使用的时候不会出现的异常,而在这些异常出现后的一段时间内,服务器会给出HTTP500的响应(MOSS服务器挂了),幸运的话过一段时间等压力降下来后服务器会自动地活过来,这现象比较神奇;倒霉的话应用程序池会自动停止,但我们一般都是RESET IIS就了事了,就是因为这样,如果没有做大量的压力测试,不容易给人发现。
经过大量的异常信息对比,我们发现异常中的代码都会与创建SPSite对象时相关联的,经过翻查SDK,有这样一段话:
If you create your own SPSite object, you can use the Dispose method or the Close method to close the object. However, if you have a reference to a shared resource, such as when the object is provided by the SPControl.GetContextSite method in a Web Part, do not use either method to close the object. In scenarios where you have a reference to a shared resource, instead let Windows SharePoint Services or your portal application manage the object. Using either method on a shared resource causes an access violation error to occur.
我们在使用SPSite的时候有两种途径能获得该对象:1. New SPSite(siteURL); 2. SPControl.GetContextSite(this.Context)
SDK的意思是说我们在使用第一种方法获得SPSite时,可以(应该说是必须)使用Close()方法或Dispose()方法关闭该对象;而使用GetContextSite()获得的SPSite对象一定不能使用那两个方法关闭该对象,使用GetContextSite()等类似的方法,如从Current对象出获得的SPSite对象,都不能进行了关闭操作,因为那是一个共享的对象,如强行关闭的话程序会出错(验证过的确会出错,当初所有代码的SPSite都没有关闭就是因为在关闭时会导致程序出错,所以没有关闭,但并没注意是用哪种方法获得的SPSite)。
很明显,共享的对象在理论一是性能是优于独立创建的一个新的对象,起码就会少创建对象的时间等操作,所以在只有一个网站集的系统(像巴陵石化项目)中,请尽量使用GetContextSite()方法获得网站集对象SPSite,以减少出错的机会及优化程序的性能。
而SPSite.Close()和SPSite.Dispose()有什么不同呢?以前看过.Net的书,Close()仅仅是释放当前对象的资源,而Dispose()在释放当前对象资源时还会调用其成员对象的Dispose()方法,以释放成员对象的资源,所以Dispose()比Close()释放得更彻底,在找这个线程异常的原因时,我也分别对Close()方法和Dispose()方法做了压力测试,使用Close()方法后,抛出线程异常的数量比没有使用任何方法时明显减少了,但还是会有;使用Dispose()方法后,就再也没有抛出该异常。我想原因是SPSite对象内有个SPWebCollections对象,包含了网站集下所有的子站点对象SPWeb,SPWeb和SPSite一样,在SDK里均有如SPSite一样的说明,所以使用SPSite.Close()方法还是不够彻底,除非你自己手工去Dispose所有的SPWeb对象。
综上所述,在使用New方法独立创建一个SPSite对象时,得写成类似下面形式:
using (SPSite site = new SPSite(siteURL or siteGUID))
{
// your code here
}
或
SPSite site = null;
try
{
site = new SPSite(siteURL or siteGUID);
// your code here
}
catch{}
finially
{
site.Dispose();
}
最后说说有关MOSS里的特权代码,就是以系统帐号身份运行某一程序段,看下面代码:
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite(web.Site.ID))
{
// do things assuming the permission of the "system account"
}
});
在使用特权代码时,必须注意在特权代码中的SPSite和SPWeb 对象必须是独立创建的对象,不能使用共享对象,否则也会抛异常,就是说不能写成像下面的代码:
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = SPControl.GetContextSite(this.Context))
{
// do things assuming the permission of the "system account"
}
});
项目中,对系统进行压力测试时,出现了大量的异常信息,类似如下面的:
Microsoft.SharePoint.SPException: Attempted to make calls on more than one thread in single threaded mode. (Exception from HRESULT: 0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD)) ---> System.Runtime.InteropServices.COMException (0x80010102): Attempted to make calls on more than one thread in single threaded mode. (Exception from HRESULT: 0x80010102 (RPC_E_ATTEMPTED_MULTITHREAD))
at Microsoft.SharePoint.Library.SPRequestInternalClass.SetVar(String bstrUrl, String bstrName, String bstrValue)
at Microsoft.SharePoint.Library.SPRequest.SetVar(String bstrUrl, String bstrName, String bstrValue)
--- End of inner exception stack trace ---
at Microsoft.SharePoint.Library.SPRequest.SetVar(String bstrUrl, String bstrName, String bstrValue)
at Microsoft.SharePoint.SPListItemCollection.EnsureListItemsData()
at Microsoft.SharePoint.SPListItemCollection.Undirty()
at Microsoft.SharePoint.SPBaseCollection.System.Collections.IEnumerable.GetEnumerator()
at OA2.MossAccessBlock.Portal.NewsHelper.GetAreaNewsDom(String subType, String area, Int32 pictureNum, Int32 newsNum) in D:\OA2_BuildVersion\MossAccessBlock\Portal\NewsHelper.cs:line 191
上面的异常的意思是说:在单线程模式下尝试进行多线程呼叫。在压力测试的过程中,出现了诸如“服务器内存不足”、“连接不到数据库服务器”、“找不到网站”等一些在单用户(用户访问量较小的情况下)使用的时候不会出现的异常,而在这些异常出现后的一段时间内,服务器会给出HTTP500的响应(MOSS服务器挂了),幸运的话过一段时间等压力降下来后服务器会自动地活过来,这现象比较神奇;倒霉的话应用程序池会自动停止,但我们一般都是RESET IIS就了事了,就是因为这样,如果没有做大量的压力测试,不容易给人发现。
经过大量的异常信息对比,我们发现异常中的代码都会与创建SPSite对象时相关联的,经过翻查SDK,有这样一段话:
If you create your own SPSite object, you can use the Dispose method or the Close method to close the object. However, if you have a reference to a shared resource, such as when the object is provided by the SPControl.GetContextSite method in a Web Part, do not use either method to close the object. In scenarios where you have a reference to a shared resource, instead let Windows SharePoint Services or your portal application manage the object. Using either method on a shared resource causes an access violation error to occur.
我们在使用SPSite的时候有两种途径能获得该对象:1. New SPSite(siteURL); 2. SPControl.GetContextSite(this.Context)
SDK的意思是说我们在使用第一种方法获得SPSite时,可以(应该说是必须)使用Close()方法或Dispose()方法关闭该对象;而使用GetContextSite()获得的SPSite对象一定不能使用那两个方法关闭该对象,使用GetContextSite()等类似的方法,如从Current对象出获得的SPSite对象,都不能进行了关闭操作,因为那是一个共享的对象,如强行关闭的话程序会出错(验证过的确会出错,当初所有代码的SPSite都没有关闭就是因为在关闭时会导致程序出错,所以没有关闭,但并没注意是用哪种方法获得的SPSite)。
很明显,共享的对象在理论一是性能是优于独立创建的一个新的对象,起码就会少创建对象的时间等操作,所以在只有一个网站集的系统(像巴陵石化项目)中,请尽量使用GetContextSite()方法获得网站集对象SPSite,以减少出错的机会及优化程序的性能。
而SPSite.Close()和SPSite.Dispose()有什么不同呢?以前看过.Net的书,Close()仅仅是释放当前对象的资源,而Dispose()在释放当前对象资源时还会调用其成员对象的Dispose()方法,以释放成员对象的资源,所以Dispose()比Close()释放得更彻底,在找这个线程异常的原因时,我也分别对Close()方法和Dispose()方法做了压力测试,使用Close()方法后,抛出线程异常的数量比没有使用任何方法时明显减少了,但还是会有;使用Dispose()方法后,就再也没有抛出该异常。我想原因是SPSite对象内有个SPWebCollections对象,包含了网站集下所有的子站点对象SPWeb,SPWeb和SPSite一样,在SDK里均有如SPSite一样的说明,所以使用SPSite.Close()方法还是不够彻底,除非你自己手工去Dispose所有的SPWeb对象。
综上所述,在使用New方法独立创建一个SPSite对象时,得写成类似下面形式:
using (SPSite site = new SPSite(siteURL or siteGUID))
{
// your code here
}
或
SPSite site = null;
try
{
site = new SPSite(siteURL or siteGUID);
// your code here
}
catch{}
finially
{
site.Dispose();
}
最后说说有关MOSS里的特权代码,就是以系统帐号身份运行某一程序段,看下面代码:
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite(web.Site.ID))
{
// do things assuming the permission of the "system account"
}
});
在使用特权代码时,必须注意在特权代码中的SPSite和SPWeb 对象必须是独立创建的对象,不能使用共享对象,否则也会抛异常,就是说不能写成像下面的代码:
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = SPControl.GetContextSite(this.Context))
{
// do things assuming the permission of the "system account"
}
});