内存不足异常 - 非托管内存
Posted
技术标签:
【中文标题】内存不足异常 - 非托管内存【英文标题】:Out Of Memory Exception - unmanaged memory 【发布时间】:2013-02-15 18:58:26 【问题描述】:我正在开发一个网络爬虫,它通常工作得很好。它将浏览大多数网站上的数千个页面,并顺利完成,没有任何问题。
在一些网站上,我反复看到同样的问题。
Insufficient memory to continue the execution of the program.
编辑: 我使用 perfmon 来确定泄漏发生在非托管内存中。 我知道是因为“私有字节”随着程序运行而不断增加,而所有堆中的字节保持稳定。
(实际上,它会上升和下降,但会逐渐攀升。它通常在我上面列出的代码部分中耗尽内存,但我不认为该部分是原因,而是可能是第一个受害者,因为它使用了很多内存......我认为它会在之后释放它)
编辑 2:
我按照此网站上的说明进行操作: http://www.codeproject.com/Articles/42721/Best-Practices-No-5-Detecting-NET-application-memo
我使用 debugDiag 来检查程序。
分析数据后,debug diag 告诉我泄漏的原因:
jscript.dll is responsible for 1.10 GBytes worth of outstanding allocations. The following are the top 2 memory consuming functions:
jscript!Parser::GenerateCode+167: 498.19 MBytes worth of outstanding allocations.
jscript!NoRelAlloc::PvAlloc+96: 292.99 MBytes worth of outstanding allocations.
我没有在我的应用程序中引用 jscript.dll,它必须被我正在使用的 Web 浏览器控件使用。
System.Windows.Forms.WebBrowser
这是我的猜测,至少。
我还收到了一个消息框,它弹出标题为“来自网页的消息”,上面写着“X 行内存不足”的意思。
所以,我认为我可以处理 webbrowser 对象并恢复我的记忆 - 所以我添加了一个带有以下代码的按钮:
Me.wbMain.Dispose() 'dispose all of thwe web-browsers
frmDebugger.wbDebugMain.Dispose()
Me.WBNewWin.Dispose()
GC.Collect() 'just for the heck of it
所以,在运行了一段时间后,我停止了抓取并单击了我的新按钮......它根本没有任何区别。我正在观看 perfmon 中的总“Private Bytes”,它甚至没有动。
有什么想法吗?
编辑 3:
我已经尝试了一堆推荐的解决方案,但似乎都没有工作。
有人建议这可能是由于图像没有从缓存中清除,但我禁用了图像加载,所以我知道这不是问题。
我也听说IE7有问题,升级到IE8就可以解决。我有 IE8,但它仍然会泄漏内存。
有人建议使用 webbrowser 控件最小化表单会释放一些内存。我试过了,没有什么影响。
我还被告知我不应该期望内存使用量会下降,因为我将不得不等待垃圾收集器。这不是托管代码中的泄漏,因此 GC.Collect() 不会做任何事情。它在非托管内存中。显然,javascript 功能使用不同的内存,并且没有手动方式来强制收集。但是它已经到了崩溃的地步,所以显然有问题。
我在这个问题上增加了 50 的赏金,我会将它奖励给任何帮助我解决泄漏的人。我想试试这个解决方案: http://www.codeproject.com/Questions/322884/WPF-WebBrowser-control-vs-Internet-Explorer-browse 但我无法弄清楚 vb.net 的等价物是什么。我尝试过在线转换器,但在转换此代码时它们会出错(尽管它们对我过去转换的其他代码工作正常)
如果我无法解决泄漏,我会将其奖励给将我上面提到的页面从 c# 转换为 vb.net 的任何人。
我的后备计划是创建一个仅包含 webbrowser 的单独应用程序,并与该进程通信,直到它内存不足,此时我将重新启动它(当我完全关闭我的应用程序时,内存被释放)。这对于我的应用程序来说远非理想,因为网络浏览器与我的项目紧密结合。
编辑 4
我尝试实施建议的 javascript 注入 - 这是我的代码:
(我在导航到新页面之前触发它)
Public Shared Sub Clean_JS(ByRef wb As System.Windows.Forms.WebBrowser)
Dim args As Object() = "document.body"
Dim head As htmlElement = wb.Document.GetElementsByTagName("head")(0)
Dim scriptEl0 As HtmlElement = wb.Document.CreateElement("script")
Dim element0 As mshtml.IHTMLScriptElement = DirectCast(scriptEl0.DomElement, mshtml.IHTMLScriptElement)
element0.text = "function ReleaseHandler() " + vbCrLf + " var EvtMgr = (function() " + vbCrLf + " var listenerMap = ;" + vbCrLf + " " + vbCrLf + " // Public interface" + vbCrLf + " return " + vbCrLf + " addListener: function(evtName, node, handler) " + vbCrLf + " node[""on"" + evtName] = handler;" + vbCrLf + " var eventList = listenerMap[evtName];" + vbCrLf + " if (!eventList) " + vbCrLf + " eventList = listenerMap[evtName] = [];" + vbCrLf + " " + vbCrLf + " eventList.push(node);" + vbCrLf + " ," + vbCrLf + " " + vbCrLf + " removeAllListeners: function() " + vbCrLf + " for (var evtName in listenerMap) " + vbCrLf + " var nodeList = listenerMap[evtName];" + vbCrLf + " for (var i = 0, node; node = nodeList[i]; i++) " + vbCrLf + " node[""on"" + evtName] = null;" + vbCrLf + " " + vbCrLf + " " + vbCrLf + " " + vbCrLf + " " + vbCrLf + " )();" + vbCrLf + " "
head.AppendChild(scriptEl0)
Dim scriptEl1 As HtmlElement = wb.Document.CreateElement("script")
Dim element1 As mshtml.IHTMLScriptElement = DirectCast(scriptEl1.DomElement, mshtml.IHTMLScriptElement)
element1.text = "function ReleaseHandler() " + vbCrLf + " var EvtMgr = (function() " + vbCrLf + " var listenerMap = ;" + vbCrLf + " " + vbCrLf + " // Public interface" + vbCrLf + " return " + vbCrLf + " addListener: function(evtName, node, handler) " + vbCrLf + " node[""on"" + evtName] = handler;" + vbCrLf + " var eventList = listenerMap[evtName];" + vbCrLf + " if (!eventList) " + vbCrLf + " eventList = listenerMap[evtName] = [];" + vbCrLf + " " + vbCrLf + " eventList.push(node);" + vbCrLf + " ," + vbCrLf + " " + vbCrLf + " removeAllListeners: function() " + vbCrLf + " for (var evtName in listenerMap) " + vbCrLf + " var nodeList = listenerMap[evtName];" + vbCrLf + " for (var i = 0, node; node = nodeList[i]; i++) " + vbCrLf + " node[""on"" + evtName] = null;" + vbCrLf + " " + vbCrLf + " " + vbCrLf + " " + vbCrLf + " " + vbCrLf + " )();" + vbCrLf + " "
head.AppendChild(scriptEl1)
wb.Document.InvokeScript("ReleaseHandler")
wb.Document.InvokeScript("purge", args)
End Sub
不幸的是,我仍然看到 perfmon 中的隐私字节在增加。
谁能看出我的逻辑有任何缺陷?我正在尝试实施此修复: http://www.codeproject.com/Questions/322884/WPF-WebBrowser-control-vs-Internet-Explorer-browse
顺便说一句 - 我用这样的简单代码对其进行了测试:
object[] args = "my important message";
webBrowser1.Document.InvokeScript("alert",args);
还有这个:
Dim head As HtmlElement = wb.Document.GetElementsByTagName("head")(0)
Dim scriptEl As HtmlElement = wb.Document.CreateElement("script")
Dim element As mshtml.IHTMLScriptElement = DirectCast(scriptEl.DomElement, mshtml.IHTMLScriptElement)
element.text = "function sayHello() alert('hello') "
head.AppendChild(scriptEl)
wb.Document.InvokeScript("sayHello")
它在两个测试用例中都显示了消息。
奇怪的是,当我尝试通过这样做来测试脚本注入时:
Dim head As HtmlElement = wbMain.Document.GetElementsByTagName("head")(0)
Dim scriptEl As HtmlElement = wbMain.Document.CreateElement("script")
Dim element As mshtml.IHTMLScriptElement = DirectCast(scriptEl.DomElement, mshtml.IHTMLScriptElement)
element.text = "function sayHello() alert('hello') "
head.AppendChild(scriptEl)
wbMain.Document.InvokeScript("sayHello")
RTB_RawHTML.Text = "TEST" + vbCrLf + wbMain.DocumentText
我没有看到文本框中反映的注入代码 - 我看到的唯一变化是出现了“测试”一词(当页面从 documentCompleted 事件完成加载时,我运行代码 RTB_RawHTML.Text = wbMain.DocumentText.. .)
【问题讨论】:
您是否尝试过调试它(按 F5)并查看导致问题的站点上涉及的实体的大小?也许明智地使用 StringBuilders 而不是 Strings 来处理经常重复使用的变量会有所帮助(如果它适用于大多数网站,似乎不太可能)。 我似乎在某处发生了非托管内存泄漏。我认为我最初查看的部分可能是受害者,而不是问题的原因......它通常会在那里崩溃,因为它实际上确实使用了大量的内存......但是,我最近注意到程序逐渐泄漏非托管内存 - 但我不知道从哪里开始。我更新了上面的帖子。 搜索“webbrowser control vb.net memory leak”有很多文章。但是,如果您只需要原始 HTML 而不是页面上的 Javascript 可能生成的任何内容,那么使用 WebClient 下载原始 HTML 会更容易和更快。 这似乎有解决办法:codeproject.com/Questions/322884/… @Allen - 参考文章中的代码是 javascript,而不是 C#。 【参考方案1】:您引用的文章中的代码不是 C#,而是 Javascript。我相信这个想法是将 JS 注入到您的 HTML 页面中,以便它可以在页面卸载时运行,这将清除现有的 JS 事件。
您可以查看这篇文章,了解如何将 JS 添加到您的 WebBrowser 控件中的页面:http://www.codeproject.com/Articles/94777/Adding-a-Javascript-Block-Into-a-Form-Hosted-by-We
Dim scriptText As String =
<string>
function ReleaseHandler()
var EvtMgr = (function()
var listenerMap = ;
// Public interface
return
addListener: function(evtName, node, handler)
node["on" + evtName] = handler;
var eventList = listenerMap[evtName];
if (!eventList)
eventList = listenerMap[evtName] = [];
eventList.push(node);
,
removeAllListeners: function()
for (var evtName in listenerMap)
var nodeList = listenerMap[evtName];
for (var i = 0, node; node = nodeList[i]; i++)
node["on" + evtName] = null;
)();
function purge(d)
var a = d.attributes, i, l, n;
if (a)
for (i = a.length - 1; i >= 0 ; i -= 1)
n = a[i].name;
if (typeof d[n] === 'function')
d[n] = null;
a = d.childNodes;
if (a)
l = a.length;
for (i = 0; i < l; i += 1)
purge(d.childNodes[i]);
<string>
Dim head As HtmlElement = webBrowser1.Document.GetElementsByTagName("head")(0)
Dim script As HtmlElement = webBrowser1.Document.CreateElement("script")
Dim domElement As IHTMLScriptElement = CType(script.DomElement, IHTMLScriptElement)
domElement.text = scriptText
head.AppendChild(script)
我还没有测试过这段代码(我不确定我会怎么做,因为你自己没有提供示例代码)......这更多的是对你如何进行的建议。我从未尝试将 JS 插入到 WebBrowser 控件中,所以我不太确定您将如何执行它(因为理论上,JS 在加载页面后已经执行,因此您注入的 JS 会“迟到”)。
您还需要找到一种连接文档的方法,以便在卸载时调用这两个函数。这个想法是通过消除 JS 对象和事件来消除 JS 内存泄漏,因此仅仅声明函数是不够的。我在网上看到很多文章讨论如何在 WebBrowser 控件中破坏 OnBeforeUnload 事件(它不能正确触发),因此您可能需要做很多工作。
【讨论】:
我确实找到了一种调用脚本的方法......它适用于简单的脚本,例如弹出一个消息框......但它不适用于相关问题。例如:wb.Document.InvokeScript("ReleaseHandler")
Dim args As Object() = "document.body"
wb.Document.InvokeScript("purge", args)
我认为您需要调用一个将 javascript 函数附加到页面卸载事件的脚本。如果我正确理解了解决方案。然而,我得到的印象是,WebBrowser 控件不是为这种用例设计的。探索替代技术可能会更好。您可能还想考虑您正在做的事情的legal ramifications(如果您还没有)。【参考方案2】:
也许您可以尝试不将 cookie 保存到用户计算机的代码。因为临时项目会给用户计算机带来几个问题
【讨论】:
cookie 由 webbrowser 控件处理的事实是我使用它的原因之一......我不想自己写那些东西。另外,我知道它必须与 javascript 相关,正如 debugdiag 告诉我的那样:jscript.dll is responsible for 1.10 GBytes worth of outstanding allocations.
以上是关于内存不足异常 - 非托管内存的主要内容,如果未能解决你的问题,请参考以下文章