.NET 4.0中的内存使用率非常高

RMD*_*RMD 63 .net c# garbage-collection rdlc .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%的利用率选址并且页面文件交换变得疯狂.

我不确定这是否与垃圾收集有关,或者是什么,但我无法搞清楚.我的窗口服务通过下面的配置文件开关使用"Server"GC:

  <runtime>
    <gcServer enabled="true"/>
  </runtime>
Run Code Online (Sandbox Code Playgroud)

将此选项更改为false似乎没有任何区别.此外,从我在4.0中的新GC上完成的阅读中,大的变化只影响工作站GC模式,而不影响服务器GC模式.因此GC可能与此问题无关.

想法?

RMD*_*RMD 85

嗯,这是一个有趣的.

根本原因是在.NET 4.0上运行时,SQL Server Reporting Services的LocalReport类(v2010)的行为发生了变化.

基本上,Microsoft改变了RDLC处理的行为,因此每次处理报告时都会在单独的应用程序域中完成.这实际上是专门为解决因无法从app域卸载程序集而导致的内存泄漏.当LocalReport类处理RDLC文件时,它实际上会动态创建一个程序集并将其加载到app域中.

就我而言,由于我正在处理大量报告,这导致创建了大量的System.Runtime.Remoting.ServerIdentity对象.这是我对事业的提示,因为我很困惑为什么处理RLDC需要远程处理.

当然,要在另一个应用程序域中的类上调用方法,远程处理正是您使用的.在.NET 3.5中,这不是必需的,因为默认情况下,RDLC程序集已加载到同一个应用程序域中.但是,在.NET 4.0中,默认情况下会创建一个新的应用程序域.

修复相当容易.首先,我需要使用以下配置启用旧版安全策略:

  <runtime>
    <NetFx40_LegacySecurityPolicy enabled="true"/>
  </runtime>
Run Code Online (Sandbox Code Playgroud)

接下来,我需要通过调用以下命令强制在与我的服务相同的应用程序域中处理RDLC:

myLocalReport.ExecuteReportInCurrentAppDomain(AppDomain.CurrentDomain.Evidence);
Run Code Online (Sandbox Code Playgroud)

这解决了这个问题.

  • 根据文档不推荐使用ExecuteReportInCurrentAppDomain.即使有了它,仍然存在内存泄漏,因为每次报表运行时,这些计算DLL都会重新创建,然后保持未使用状态但不会被卸载.我们发现解决这个问题的唯一方法是每次我们想要运行报告时生成一个新进程.当进程终止时,EVERYTHING会自动释放,包括不再需要的DLL.您只需要考虑使用MS报告. (6认同)
  • 这很疯狂,人们怎么不对这个问题大喊大叫呢?它仍然存在于.NET 4.5中. (4认同)
  • 如果将RDLC处理移动到其他应用程序域以阻止内存泄漏,您为什么要将其移至当前应用程序域?似乎是另一个内存泄漏的折衷. (3认同)
  • 我不确定你是如何追踪这个修复的,但是很勇敢! (2认同)

小智 12

我遇到了这个问题.确实,app域是创建的而不是清理的.但是,我不建议恢复遗产.它们可以通过ReleaseSandboxAppDomain()进行清理.

LocalReport report = new LocalReport();
...
report.ReleaseSandboxAppDomain();
Run Code Online (Sandbox Code Playgroud)

我还要做的其他一些事情要清理:

取消订阅任何SubreportProcessing事件,清除数据源,处理报告.

我们的Windows服务每秒处理几个报告,没有泄漏.

  • 谢谢,在测试您上面所说的内容后,我看到报告不再泄漏。:) 这就是我所做的。我希望它可以帮助其他人: localReport.SubreportProcessing -= reportDataProcessor.SubreportProcessingHandler; localReport.DataSources.Clear(); localReport.ReleaseSandboxAppDomain(); localReport.Dispose(); (2认同)

Dan*_*enz 5

我已经很晚了,但我有一个真正的解决方案并且可以解释原因!

事实证明,LocalReport 这里使用 .NET Remoting 动态创建子应用程序域并运行报告,以避免内部某处泄漏。然后我们注意到,最终报告将在 10 到 20 分钟后释放所有内存。对于生成大量 PDF 的人来说,这是行不通的。然而,这里的关键是他们正在使用.NET Remoting。远程处理的关键部分之一是所谓的“租赁”。租赁意味着它将将该 Marshal 对象保留一段时间,因为远程处理通常设置成本昂贵,并且可能会多次使用。LocalReport RDLC 正在滥用这一点。

默认情况下,租用时间是……10分钟!另外,如果有东西发出不同的调用,则会额外增加 2 分钟的等待时间!因此,它可以随机地介于 10 到 20 分钟之间,具体取决于呼叫的排队方式。幸运的是,您可以更改超时发生的时间。不幸的是,每个应用程序域只能设置一次...因此,如果您需要远程处理而不是 PDF 生成,您可能需要让另一个服务运行它,以便您可以更改默认值。为此,您只需在启动时运行以下 4 行代码:

    LifetimeServices.LeaseTime = TimeSpan.FromSeconds(5);
    LifetimeServices.LeaseManagerPollTime = TimeSpan.FromSeconds(5);
    LifetimeServices.RenewOnCallTime = TimeSpan.FromSeconds(1);
    LifetimeServices.SponsorshipTimeout = TimeSpan.FromSeconds(5);
Run Code Online (Sandbox Code Playgroud)

您会看到内存使用量开始上升,然后在几秒钟内您应该会看到内存开始下降。我花了几天时间使用内存分析器来真正追踪并了解发生了什么。

您无法将 ReportViewer 包装在 using 语句中(Dispose 崩溃),但如果直接使用 LocalReport,则应该可以。在处理之后,如果您想双重确定您正在尽一切努力释放该内存,则可以调用 GC.Collect()。

希望这可以帮助!

编辑

显然,您应该在生成 PDF 报告后调用 GC.Collect(0),否则由于某种原因,内存使用量似乎仍然会很高。