.NET 4.0中的内存使用量非常高
我有一个C#Windows服务,我最近从.NET 3.5转移到.NET 4.0。 没有其他代码更改。
在3.5上运行时,给定工作负载的内存利用率大约为1.5 GB内存,吞吐量为每秒20 X。 (X在这个问题的背景下并不重要。)
运行在4.0上的完全相同的服务使用3GB至5GB +的内存,并且每秒不到4X。 事实上,随着内存使用量的不断攀升,服务通常会停滞不前,直到我的系统使用率达到99%,并且页面文件交换变得很糟糕。
我不确定这是否与垃圾回收有关,或者是什么,但我很难搞清楚。 我的窗口服务使用“服务器”GC通过configuration文件开关如下所示:
<runtime> <gcServer enabled="true"/> </runtime>
将此选项更改为false似乎没有什么区别。 另外,从4.0版新增的GC中读取的数据,大的变化只影响工作站的GC模式,而不是服务器的GC模式。 所以也许GC与这个问题无关。
想法?
那么这是一个有趣的。
根本原因原来是在.NET 4.0之上运行时,SQL Server Reporting Services的LocalReport类(v2010)的行为发生了变化。
基本上,微软改变了RDLC处理的行为,以便每次处理报告时都是在单独的应用程序域中完成的。 这实际上是专门解决由于无法从应用程序域卸载程序集而造成的内存泄漏。 当LocalReport类处理RDLC文件时,它实际上会创build一个程序集并将其加载到应用程序域中。
就我而言,由于我正在处理大量的报表,导致创build了大量的System.Runtime.Remoting.ServerIdentity对象。 这是我的原因,因为我很困惑,为什么处理RLDC需要远程处理。
当然,要调用另一个应用程序域中的类的方法,远程处理正是你所使用的。 在.NET 3.5中,这是没有必要的,因为默认情况下,RDLC程序集被加载到相同的应用程序域。 但是,在.NET 4.0中,默认情况下会创build一个新的应用程序域。
修复相当简单。 首先,我需要使用以下configuration启用传统安全策略:
<runtime> <NetFx40_LegacySecurityPolicy enabled="true"/> </runtime>
接下来,我需要强制RDLC在与我的服务相同的应用程序域中处理,方法如下:
myLocalReport.ExecuteReportInCurrentAppDomain(AppDomain.CurrentDomain.Evidence);
这解决了这个问题。
我遇到了这个确切的问题。 确实,应用程序域被创build,而不是清理。 不过,我不build议恢复到传统。 它们可以通过ReleaseSandboxAppDomain()进行清理。
LocalReport report = new LocalReport(); ... report.ReleaseSandboxAppDomain();
我还做了一些其他的事情来清理:
取消订阅任何SubreportProcessing事件,清除数据源,处理报告。
我们的Windows服务处理几个报告,没有泄漏。
你可能想要
- 剖析堆
- 使用WinDbg + SOS.dll来确定哪些资源正在泄漏以及从何处引用
也许有些API已经改变了语义,或者在4.0版本的框架中甚至可能存在一个bug
为了完整ASP.Net web.config
,如果有人正在寻找等效的ASP.Net web.config
设置,那么:
<system.web> <trust legacyCasModel="true" level="Full"/> </system.web>
ExecuteReportInCurrentAppDomain
工作原理是一样的。
感谢这个社交MSDN参考 。
看起来好像微软试图将报告放入其独立的内存空间来解决所有的内存泄漏问题而不是解决问题。 在这样做的时候,他们引入了一些严重的崩溃,最终导致了更多的内存泄漏。 他们似乎caching了报告的定义,但从来没有使用过,也从来没有清理过,每一个新的报告都会创build一个新的报告定义,占用越来越多的内存。
我玩弄同样的事情:使用一个单独的应用程序域,并编组报告。 我认为这是一个可怕的解决办法,并且很快就搞砸了。
我所做的是相似的:把你的程序的报告部分分成它自己的单独的报告程序。 无论如何,这是组织代码的好方法。
棘手的部分是将信息传递给单独的程序。 使用Process
类启动报告程序的新实例,并在命令行上传递它所需的任何参数。 第一个参数应该是一个枚举或类似的值,表示应该打印的报告。 我在主程序中的代码如下所示:
const string sReportsProgram = "SomethingReports.exe"; public static void RunReport1(DateTime pDate, int pSomeID, int pSomeOtherID) { RunWithArgs(ReportType.Report1, pDate, pSomeID, pSomeOtherID); } public static void RunReport2(int pSomeID) { RunWithArgs(ReportType.Report2, pSomeID); } // TODO: currently no support for quoted args static void RunWithArgs(params object[] pArgs) { // .Join here is my own extension method which calls string.Join RunWithArgs(pArgs.Select(arg => arg.ToString()).Join(" ")); } static void RunWithArgs(string pArgs) { Console.WriteLine("Running Report Program: {0} {1}", sReportsProgram, pArgs); var process = new Process(); process.StartInfo.FileName = sReportsProgram; process.StartInfo.Arguments = pArgs; process.Start(); }
报告程序看起来像这样:
[STAThread] static void Main(string[] pArgs) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var reportType = (ReportType)Enum.Parse(typeof(ReportType), pArgs[0]); using (var reportForm = GetReportForm(reportType, pArgs)) Application.Run(reportForm); } static Form GetReportForm(ReportType pReportType, string[] pArgs) { switch (pReportType) { case ReportType.Report1: return GetReport1Form(pArgs); case ReportType.Report2: return GetReport2Form(pArgs); default: throw new ArgumentOutOfRangeException("pReportType", pReportType, null); } }
您的GetReportForm
方法应该拉取报表定义,利用相关参数获取数据集,将数据和任何其他parameter passing给报表,然后将报表放置在表单的报表查看器中,并返回对表单的引用。 请注意,可以提取这个过程的大部分内容,这样就可以基本上说'使用这些数据和这些参数给我一个来自这个程序集的报告的表单。
还要注意,这两个程序都必须能够看到与这个项目相关的数据types,所以希望你已经把你的数据类提取到他们自己的库中,这两个程序都可以共享一个引用。 在主程序中没有所有的数据类,因为主程序和报表程序之间会有一个循环依赖关系。
也不要过多地争论。 在报告程序中查询您需要的任何数据库; 不要通过一个巨大的对象列表(这可能无法正常工作)。 你应该只是传递简单的东西,如数据库ID字段,date范围等。如果你有特别复杂的参数,你可能也需要将UI的这一部分推到报表程序,而不是在命令行上作为parameter passing它们。
您还可以在主程序中添加对报告程序的引用,并将生成的.exe和任何相关的.dll文件复制到同一个输出文件夹中。 然后你可以运行它而不指定path,只使用自己的可执行文件名(例如:“SomethingReports.exe”)。 您还可以从主程序中删除报告dll。
与此有关的一个问题是,如果您从未真正发布报告程序,您将会遇到明显的错误。 只是假发布一次,生成一个清单,然后将工作。
一旦你有这个工作,打印一个报告,看到你的普通程序的内存保持不变是非常好的。 报告程序出现了,占用了比主程序更多的内存,然后消失,完全清除它,主程序占用的内存比现有的还多。
另一个问题可能是每个报表实例现在占用的内存比以前多,因为它们现在是完全独立的程序。 如果用户打印大量报告并且从不closures它们,则会非常快地耗尽大量内存。 但是我认为这样做还是好多了,因为只要closures报告就可以轻松回收记忆。
这也使得你的报告独立于你的主程序。 即使在closures主程序之后,它们也可以保持打开状态,并且可以从命令行手动生成,也可以从其他源生成它们。