【发布时间】:2014-07-15 07:26:09
【问题描述】:
我有一个程序,它接受 SQL 查询作为命令行参数,查询 PostgreSQL 数据库并生成以多种方式之一格式化的文件(通常用于生成 CSV 文件)。
然而,这个程序有一些严重的内存泄漏——一个特定的查询产生 12MB 的文件,在操作系统杀死它之前,该程序使用了 8GB 的 RAM 加上几 GB 的交换空间。我想找出这个内存泄漏的原因。我不太了解 Delphi(从程序的质量来看,原作者也不了解),但我的任务是找到一个快速解决方案。
以下doData 函数部分输出结果集的单行。我冒险猜测问题出在“复制”命令(在堆上创建一个从未释放的字符串),但我相信比我更有经验的人将能够确认这个答案或指出我正确的方向。
procedure doData;
var
s, fldVal : string;
i, fldLen : integer;
begin
s := '';
for i := 0 to ds.Fields.Count-1 do
begin
if (ds.Fields[i].DataType = ftDate) or
(ds.Fields[i].DataType = ftDateTime) then
begin
if psql.outDate = 'i' then
fldLen := 8
else
fldLen := 10;
if ds.Fields[i].IsNull then
fldVal := ''
else
fldVal := formatDate(ds.Fields[i].AsDateTime);
end
else
begin
fldLen := ds.Fields[i].DisplayWidth;
fldVal := ds.Fields[i].AsString;
end;
if (psql.outType = 'd') or (psql.outType = 's') then
s := s + trim(fldVal)
else if psql.outType = 'f' then
begin
s := s + fldVal;
if fldLen - length(fldVal) > 0 then
s := s + copy(spaces, 1, fldLen - length(fldVal));
// Is this a memory leak above?
end;
if psql.outType = 's' then
begin
if i < ds.Fields.Count-1 then
s := s + psql.outDelimChar;
end
else
s := s + psql.outDelimChar;
end;
writeln(psql.outPrefixData + s);
end;
【问题讨论】:
-
Delphi 字符串类型由引用计数管理。当字符串变量超出范围时,它会自动销毁。
Copy无法在此代码中造成内存泄漏。 -
如果您正在生成一个 12MB 的字符串,那么您做错了:改用 TStringBuilder(或直接写入流)。但是使用像
s := s + xx这样的代码对内存处理非常不利 -
我发现
MadExcept是识别内存泄漏的有效工具,但从您的代码片段开始,我可能会检查ds和psql在哪里被释放-您可能会很幸运。 .. -
我以前通过在某些位置放置断点并观察任务管理器中的内存使用量增加来诊断出像这样的严重内存泄漏。 (请注意,可能需要一秒钟才能显示,Delphi 内存管理器不会立即释放回操作系统等,这可能会使它复杂化。)但是在它只会上升并且每个增量都很大的情况下,通常很容易找到导致它的一段代码。然后将其缩小为函数、函数的一部分和行。
-
我的猜测也不是字符串(正如 LU RD 解释的那样),而是对象引用持有的时间太长 - 即,没有尽早释放。我的猜测是数据库查询中的数据副本,即程序只是将所有内容保存在内存中,而不是对每个字段执行任何操作并在迭代到下一个之前让该数据继续。不过,这只是一个猜测。我在上面的代码中看不到任何明显的东西。根据我上面的评论缩小范围。
标签: string delphi memory-leaks copy