为什么位图会留在内存中,除非我调用GC.Collect
本文关键字:非我 调用 GC Collect 位图 内存 为什么 | 更新日期: 2023-09-27 18:10:58
我正在开发一个连接到GigEVision相机的应用程序,并从中提取图像。我目前正在使用Pleora eBus SDK与c# . net。
下面的代码只是一个相机连接的测试应用程序-它可以流图像,但很快耗尽内存,除非我调用GC.Collect();值得注意的是,正在流式传输的图像很大(4096x3072),因此崩溃发生得相当快。
一开始我怀疑没有调用Dispose()是问题所在。但是,我可以在删除对每个图像的引用之前对其调用Dispose(),这并没有解决问题。
我也试过显式地释放进入显示线程回调的缓冲区,但没有效果。
我可以用一种更优雅的方式找回我的记忆吗?
using System;
using System.Windows.Forms;
using PvDotNet;
using PvGUIDotNet;
using System.Drawing;
namespace eBus_Connection
{
public partial class MainForm : Form
{
PvDeviceGEV camera;
PvStreamGEV stream;
PvPipeline pipeline;
PvDisplayThread thread;
bool updating = false;
public MainForm()
{
InitializeComponent();
}
private void MainForm_Shown(object sender, EventArgs e)
{
PvDeviceInfo info;
PvDeviceFinderForm form = new PvDeviceFinderForm();
form.ShowDialog();
info = form.Selected;
camera = PvDeviceGEV.CreateAndConnect(info) as PvDeviceGEV;
stream = PvStreamGEV.CreateAndOpen(info.ConnectionID) as PvStreamGEV;
pipeline = new PvPipeline(stream);
if (camera == null || stream == null)
throw new Exception("Camera or stream could not be created.");
camera.NegotiatePacketSize();
camera.SetStreamDestination(stream.LocalIPAddress, stream.LocalPort);
camera.StreamEnable();
camera.Parameters.ExecuteCommand("AcquisitionStart");
pipeline.Start();
thread = new PvDisplayThread();
thread.OnBufferDisplay += thread_OnBufferDisplay;
thread.Start(pipeline, camera.Parameters);
status.DisplayThread = thread;
status.Stream = stream;
}
void thread_OnBufferDisplay(PvDisplayThread aDisplayThread, PvBuffer aBuffer)
{
Bitmap b = new Bitmap((int)aBuffer.Image.Width, (int)aBuffer.Image.Height);
aBuffer.Image.CopyToBitmap(b);
BeginInvoke(new Action<Bitmap>(ChangeImage), b);
}
void ChangeImage(Bitmap b)
{
if (PictureBox.Image != null)
PictureBox.Dispose();
PictureBox.Image = b;
GC.Collect();//taking this away causes memory to leak rapidly.
}
}
}
很可能在代码中的某个地方没有处理Image
,例如Bitmap
。Bitmap
扩展Image
,实现IDisposable
,这意味着你需要调用Dispose()
,当你完成它(通常通过包装它与using
语句)。您没有将Bitmap
或Image
丢弃在某个地方,以便GC在可能的时候结束它(或者在这种情况下,当您显式调用GC时)。
一旦GC确定一个类不再被引用,它就可以被清理…在清理之前,它检查终结器。如果存在终结器,则该类将被放置在一个特殊的GC终结器队列中,该队列将在清理资源/内存之前运行该终结器。大多数IDisposable
类都有终结器,在您忘记自己手动处置类的情况下,允许GC执行Dispose()
调用工作。这似乎是发生在你的代码,但没有看到所有的类,我只能猜测什么是不处置(不知道在哪里)。
编辑:我确实有一个猜测。我打赌PictureBox.Dispose()
调用不会处理PictureBox.Image
如果一个对象实现了IDisposable
,那么你绝对应该在它上面调用Dispose
,但是释放一个对象并不会释放它占用的内存。它释放一些东西,比如,在这个例子中,一个图像句柄。在回收内存之前,必须先释放这些资源,因此释放这些资源仍然有帮助。
当GC运行时,如果一个对象还没有被处理,那么它必须首先完成它,这意味着它必须等待更长的时间来回收内存。如果对象已被处理,则在GC运行时立即回收内存。
GC在后台运行。如果您的应用程序忙于分配越来越多的内存,那么无论您是否处置对象,GC都永远没有机会运行和回收内存。在这种情况下,您需要不时显式地调用GC。创建多个映像是最常见的需要显式GC调用的场景。
值得注意的是,所有对象都保留在内存中,直到GC运行并清理它们,无论对象是否实现了IDisposable
。你通常不会注意到它,因为大多数应用程序有足够的停机时间来允许GC隐式运行并回收内存。在这方面,Bitmap
对象没有什么特别之处。
您正在处理图片框而不是图像。即使这将处理图片框中的图像,它也只会在第一次这样做。之后,图片框处于已处理状态,再次调用Dispose
将不做任何事情。
你应该从图片框中获取图像参考,并在不再使用时处理它:
void ChangeImage(Bitmap b) {
Image oldImage = PictureBox.Image;
PictureBox.Image = b;
if (oldImage != null) {
oldImage.Dispose();
}
}
未正确处理的位图必须在收集之前完成。有一个后台线程完成需要收集的对象,但是如果你放弃对象的速度比该线程处理它们的速度快,你就会耗尽内存。
当位图被正确处理后,它就变成了一个常规的托管对象,可以在垃圾收集器想要的时候立即收集。