晚上好!
在当前的项目中,我遇到了一个相当令人担忧的内存泄漏,我似乎无法插上。
我让应用程序在标准使用情况下一夜之间运行,当我8小时后醒来时,它耗尽了大约750MB的内存,而它开始时只需50MB。 Windows任务管理器不适用于检查泄漏,除了允许您首先发现存在泄漏。
我已经清除了一些其他内存泄漏,主要内容与Firemonkeys的TGlowEffect
相关。 ReportLeaksOnShutdown
未检测到它,但动态修改对象的内存使用量变得非常过分(例如旋转或缩放更改)。
我已将其追踪到计时器(并且禁用它可以完全阻止泄漏),如果可能,我需要协助修复它。
说明:此代码使用Firemonkey MakeScreenshot
功能将TPanel (SigPanel)
的视觉外观保存到TMemoryStream
。然后使用标准代码将此流数据上载到远程FTP服务器(见下文)。在SigPanel
内,有4个TLabel
个孩子,1个TRectangle
个孩子和6个TImage
个孩子。
注释:CfId
是一个全局字符串,它是根据随机extended
浮点值生成的,然后以yyyymmdd_hhnnsszzz
格式与DateTime一起进行哈希处理。这一代在创建表单时完成,并且一直重复,直到它变为有效CfId
(即不包含在Windows文件名中使用的非法字符)。一旦它获得有效的CfId
,它就不会再次运行(因为我不再需要生成新的ID)。这使我几乎可以完全消除重复CfId
的可能性。
计时器中的代码如下;
var
i : Integer;
SigStream : TMemoryStream;
begin
SigStream := TMemoryStream.Create;
SigPanel.MakeScreenshot.SaveToStream(SigStream);
SigPanel.MakeScreenshot.Free;
if VT2SigUp.Connected then
begin
VT2SigUp.Put(SigStream,'Sig_'+CfId+'.png',False);
end else
begin
VT2SigUp.Connect;
VT2SigUp.Put(SigStream,'Sig_'+CfId+'.png',False);
end;
SigStream.Free;
end;
当计时器 NOT 运行时,代码完全没有泄漏,ReportMemoryLeaksOnShutdown
NOT 生成消息。启用定时器并允许至少“运行”一次,我得到大量泄漏,这会增加计时器运行的次数。报告的泄漏情况如下;
Small Block Leaks
1 - 12 Bytes: Unknown x 1
13 - 20 Bytes: TList x 5, Unknown x 1
21 - 28 Bytes: TFont x 2, TGradientPoint x 8, TGradientPoints x 4, Unknown x 4
29 - 36 Bytes: TObjectList<FMX.Types.TCanvasSaveState> x 1, TBrushBitmap x 4,
TBrushGrab x 4, TPosition x 24, TGradient x 4, UnicodeString x1
37 - 44 Bytes: TBrushResource x 4
53 - 60 Bytes: TBrush x 4
61 - 68 Bytes: TBitmap x 5
69 - 76 Bytes: TD2DCanvasSaveState x 1
205 - 220 Bytes: TCanvasD2D x 1
Sizes of Medium and Large Block Leaks
200236
当计时器运行时,这些值乘以 n 次( n 是计时器运行的次数)。中型和大型块的 n 值为200236(例如,如果计时器已运行3次,则 200236,200236,200326 )。
有趣的是,如果我删除与MakeScreenshot
相关联的代码,则泄漏不再存在,并且内存使用量保持在某种正常水平。除了通常的内存使用情况外,没有任何异常情况,也没有泄漏报告。我已经尝试了多个代码示例,既可以保存到流中,也可以从那里上传,或者保存到流&gt;文件然后上传文件,但函数本身内似乎有泄漏。我甚至在这里发现泄漏时添加了MakeScreenshot.Free
,但我似乎无法插入它,当然,我在我的一个代码“test runs”中使用了try..finally
。 / p>
我甚至用GDI +作为画布类型运行代码,并且在那里发生同样的泄漏(唯一的变化是D2D泄漏引用GDI +)。
我非常感谢任何人对此有任何研究或笔记,以及解决问题的方法。
答案 0 :(得分:14)
您没有释放MakeScreenshot
创建的位图。
procedure TForm1.Button1Click(Sender: TObject);
var
ms: TMemoryStream;
begin
ms := TMemoryStream.Create;
Panel1.MakeScreenshot.SaveToStream(ms);
ms.Free;
end;
上面的代码没有保留对创建的位图的引用,因此没有机会释放它。而是改变你的设计,如下所示:
procedure TForm1.Button2Click(Sender: TObject);
var
ms: TMemoryStream;
bmp: TBitmap;
begin
ms := TMemoryStream.Create;
bmp := Panel1.MakeScreenshot;
bmp.SaveToStream(ms);
ms.Free;
bmp.Free;
end;
使用下面的代码,您实际上创建了两个位图并释放其中一个位图。
SigPanel.MakeScreenshot.SaveToStream(SigStream);
SigPanel.MakeScreenshot.Free;
最后,您的代码将更像下面的内容:
var
i : Integer;
Bmp: TBitmap;
SigStream : TMemoryStream;
begin
SigStream := TMemoryStream.Create;
try
Bmp := SigPanel.MakeScreenshot;
try
Bmp.SaveToStream(SigStream);
if not VT2SigUp.Connected then
VT2SigUp.Connect;
VT2SigUp.Put(SigStream, 'Sig_'+CfId+'.png', False);
finally
Bmp.Free;
end;
finally
SigStream.Free;
end;
end;