У меня есть приложение, которое используется для обработки изображений, и я нахожу себя обычно выделяющим массивы в размере usxort 4000x4000, а также случайное смещение и тому подобное. В настоящее время .NET Framework имеет тенденцию к аварийному завершению в этом приложении, по-видимому, случайно, почти всегда с ошибкой нехватки памяти. 32 МБ не является огромным объявлением, но если .NET фрагментирует память, то вполне возможно, что такие большие непрерывные распределения не будут работать, как ожидалось.
Есть ли способ заставить сборщик мусора быть более агрессивным или дефрагментировать память (если в этом проблема)? Я понимаю, что есть вызовы GC.Collect и GC.WaitForPendingFinalizers, и я довольно обильно обсыпал их через мой код, но я все еще получаю ошибки. Это может быть потому, что я вызываю подпрограммы dll, которые часто используют нативный код, но я не уверен. Я просмотрел этот код C ++ и убедился, что любую объявленную мной память я удаляю, но все же я получаю эти C # сбои, так что я почти уверен, что ее там нет. Интересно, могут ли вызовы C ++ мешать GC, оставляя его позади, потому что он когда-то взаимодействовал с собственным вызовом - возможно ли это? Если да, могу ли я отключить эту функцию?
РЕДАКТИРОВАТЬ: Вот очень специфический код, который вызовет сбой. Согласно этому SO вопросу , мне не нужно избавляться от объектов BitmapSource здесь. Вот наивная версия, в ней нет GC. Собирает. Обычно происходит сбой на итерации 4-10 процедуры отмены. Этот код заменяет конструктор в пустом проекте WPF, так как я использую WPF. Я делаю дурацкие действия с битмапсорсером из-за ограничений, которые я объяснил в своем ответе на @dthorpe ниже, а также требований, перечисленных в этом вопросе SO .
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
//Attempts to create an OOM crash
//to do so, mimic minute croppings of an 'image' (ushort array), and then undoing the crops
int theRows = 4000, currRows;
int theColumns = 4000, currCols;
int theMaxChange = 30;
int i;
List<ushort[]> theList = new List<ushort[]>();//the list of images in the undo/redo stack
byte[] displayBuffer = null;//the buffer used as a bitmap source
BitmapSource theSource = null;
for (i = 0; i < theMaxChange; i++) {
currRows = theRows - i;
currCols = theColumns - i;
theList.Add(new ushort[(theRows - i) * (theColumns - i)]);
displayBuffer = new byte[theList[i].Length];
theSource = BitmapSource.Create(currCols, currRows,
96, 96, PixelFormats.Gray8, null, displayBuffer,
(currCols * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
System.Console.WriteLine("Got to change " + i.ToString());
System.Threading.Thread.Sleep(100);
}
//should get here. If not, then theMaxChange is too large.
//Now, go back up the undo stack.
for (i = theMaxChange - 1; i >= 0; i--) {
displayBuffer = new byte[theList[i].Length];
theSource = BitmapSource.Create((theColumns - i), (theRows - i),
96, 96, PixelFormats.Gray8, null, displayBuffer,
((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
System.Console.WriteLine("Got to undo change " + i.ToString());
System.Threading.Thread.Sleep(100);
}
}
}
Теперь, если я явно вызываю сборщик мусора, мне нужно обернуть весь код во внешний цикл, чтобы вызвать сбой OOM. Для меня это обычно происходит около х = 50 или около того:
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
//Attempts to create an OOM crash
//to do so, mimic minute croppings of an 'image' (ushort array), and then undoing the crops
for (int x = 0; x < 1000; x++){
int theRows = 4000, currRows;
int theColumns = 4000, currCols;
int theMaxChange = 30;
int i;
List<ushort[]> theList = new List<ushort[]>();//the list of images in the undo/redo stack
byte[] displayBuffer = null;//the buffer used as a bitmap source
BitmapSource theSource = null;
for (i = 0; i < theMaxChange; i++) {
currRows = theRows - i;
currCols = theColumns - i;
theList.Add(new ushort[(theRows - i) * (theColumns - i)]);
displayBuffer = new byte[theList[i].Length];
theSource = BitmapSource.Create(currCols, currRows,
96, 96, PixelFormats.Gray8, null, displayBuffer,
(currCols * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
}
//should get here. If not, then theMaxChange is too large.
//Now, go back up the undo stack.
for (i = theMaxChange - 1; i >= 0; i--) {
displayBuffer = new byte[theList[i].Length];
theSource = BitmapSource.Create((theColumns - i), (theRows - i),
96, 96, PixelFormats.Gray8, null, displayBuffer,
((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
GC.WaitForPendingFinalizers();//force gc to collect, because we're in scenario 2, lots of large random changes
GC.Collect();
}
System.Console.WriteLine("Got to changelist " + x.ToString());
System.Threading.Thread.Sleep(100);
}
}
}
Если я неправильно обращаюсь с памятью в любом из сценариев, если я что-то обнаружу с помощью профилировщика, дайте мне знать. Там довольно простая рутина.
К сожалению, похоже, ответ @ Кевина верный - это ошибка в .NET и то, как .NET обрабатывает объекты размером более 85 КБ. Эта ситуация кажется мне чрезвычайно странной; Можно ли переписать Powerpoint в .NET с таким ограничением или в любом другом приложении Office? 85k не кажется мне большим пространством, и я также думаю, что любая программа, которая часто использует так называемые «большие» выделения, может стать нестабильной в течение нескольких дней или недель при использовании .NET.
EDIT : Похоже, Кевин прав, это ограничение GC .NET. Для тех, кто не хочет следовать всему потоку, в .NET есть четыре кучи GC: gen0, gen1, gen2 и LOH (куча больших объектов). Все, что 85k или меньше, идет в одну из первых трех куч, в зависимости от времени создания (перемещено от gen0 к gen1 к gen2 и т. Д.). Объекты размером более 85 тыс. Размещаются на LOH. LOH никогда не уплотняется, поэтому в конечном итоге выделение того типа, который я делаю, в конечном итоге приведет к ошибке OOM, так как объекты разбросаны по этому пространству памяти. Мы обнаружили, что переход на .NET 4.0 действительно помогает решить проблему, задерживая исключение, но не предотвращая его. Честно говоря, это похоже на барьер 640 Кб - 85 Кбайт должно хватить для любого пользовательского приложения (перефразируя это видео обсуждения GC в .NET). Напомним, что Java не демонстрирует такого поведения со своим GC.