由于看似过早的Out of Memory异常,我们一直在仔细研究各种.NET构造的内存使用情况......尤其是大型对象,这些对象往往会破坏大对象堆,从而导致过早的Out of Memory异常。一个有点令人惊讶的领域是.NET Image类:Bitmap和Metafile。
以下是我们认为已经学到的内容,但无法找到要验证的MS文档,因此我们希望其他人可以给予任何确认:
(1)当您从压缩的光栅文件(JPG,PNG,GIF等)创建Bitmap对象时,它会以该文件的完整分辨率为完全未压缩的像素阵列消耗内存。因此,例如,9000x3000像素的5MB JPG将扩展为9000x3000x3字节(假设为24位颜色,无alpha)或消耗81MB内存。正确的吗?
(1a)有一些证据(见下面的2b)它也存储了原始的压缩格式......所以,在这种情况下实际上是86MB。但那还不清楚......有人知道吗?
(2)当你创建一个Metafile对象,然后在其中绘制一个光栅文件(JPG,PNG,GIF等)时,它只消耗压缩文件的内存。因此,如果你在一个图元文件中绘制一个9000x3000像素的5MB JPG,它只会消耗大约5MB的内存。正确的吗?
(2a)要将光栅文件绘制到Metafile对象中,唯一的方法似乎是使用该文件加载Bitmap,然后将Bitmap绘制到元文件中。有没有更好的方法不涉及临时加载那个巨大的Bitmap数据(并导致相关的内存碎片)?
(2b)当您将位图绘制到图元文件时,它使用的压缩格式大小与原始压缩文件类似。它是通过将原始压缩文件存储在位图中来实现的吗?或者它是通过使用原始压缩设置重新压缩扩展的位图来实现的?
(3)我们最初假设大对象(> 85KB)将放置在大对象堆中。事实上,情况似乎并非如此。相反,每个Bitmap和每个Metafile都是Small Object Heap中的一个24字节对象,它指的是包含真实数据的Native Memory块。正确的吗?
(3a)我们假设这样的Native Memory就像Large Object Heap一样,它不能被压缩......一旦大对象被放入Native Memory,它就永远不会被移动,因此Native Memory的碎片可能会导致许多问题,如大对象堆碎片。真正?或者是否有更有效的底层Bitmap / Metafile数据的特殊处理?
(3b)因此,似乎有四个独立的内存块被单独管理,并且每个内存的运行都会导致相同的Out of Memory异常:Small Object Heap(托管对象<85KB,由GC),大对象堆(由GC收集但未压缩的托管对象> 85KB),本机内存(非托管对象,可能未压缩)和桌面堆(窗口处理和管理此类有限资源)。我是否正确记录了这四个?我们应该注意其他人吗?
任何人都可以提供上述任何清晰度,我们将不胜感激。如果有完整解释上述内容的好书或文章,请告诉我。 (我很乐意做必要的阅读;但绝大多数书都没有那么深,所以不要告诉我任何我不知道的东西。)
谢谢!
答案 0 :(得分:4)
存储图像数据的方法有两种:像素或矢量。 Bitmap
与像素有关,Metafile
与像素和向量相关。存储矢量数据的效率要高得多。
为了允许操作位图,它们的数据必须以未压缩的方式存储在内存中。否则GetPixel
和SetPixel
必须解压缩,更改,重新压缩每个更改的位图(如果甚至可以开始)。
Metafiles由Microsoft创建,旨在与GDI协同工作,因此它可能包含一些直接与图形卡一起使用的内存效率更高的压缩算法。此外,元文件没有GetPixel
SetPixel
方法,因此不必在内存中解压缩以允许操作。
您不必关心运行时使用的内存池。还有更多,运行时决定它放置对象的位置。此外,您不应该关心使用(大)对象可能可能引起的内存不足异常。运行时将尽其所能(将对象放在其他对象之间的间隙中,压缩堆,扩展可用的虚拟内存)以确保不会出现内存不足异常。如果你以某种方式得到这样的异常,你的代码中可能存在另一个应该修复的问题(例如内存泄漏)。
内存堆,地图和表格的概述:(source)
此外,您假设超过85 KiB的对象放置在大对象堆上并不完全正确。对于当前版本的CLR中的大多数对象是正确的,但是例如在大对象堆上也分配了8 KiB的双精度数组(1000双精度)。让运行时关注自己。
答案 1 :(得分:2)
我知道其中一些的答案:
(1)是的,这是位图图像的定义。
(3)是的,这就是Bitmap实现IDisposable接口的原因。
(3a)这似乎令人惊讶。你完成它们后,你是否在Bitmap对象上运行Dispose()方法?
(3b)至少那四个,是的。