OutOfMemoryException при заполнении MemoryStream: выделение 256 МБ на 16 ГБ системе

Я запускаю следующий метод на моем сервере IIS разработки (из VS2010 IDE) на 64-разрядной машине с Windows 7 с 16 ГБ установленной оперативной памяти:

public static MemoryStream copyStreamIntoMemoryStream(Stream stream)
{
    long uiLen = stream.Length;
    byte[] buff = new byte[0x8000];

    int nSz;
    MemoryStream ms = new MemoryStream();
    try
    {
        while ((nSz = stream.Read(buff, 0, buff.Length)) != 0)
        {
            ms.Write(buff, 0, nSz);
        }
    }
    finally
    {
        Debug.WriteLine("Alloc size=" + ms.Length);
    }

    return ms;
}

и я получаю System.OutOfMemoryException в этой строке:

ms.Write(buff, 0, nSz);

Это выдается, когда выделяется 268435456 байт:

Alloc size = 268435456

который равен 0x10000000 или 256 МБ. Поэтому мне интересно, есть ли какие-то глобальные настройки, которые мне нужно настроить, чтобы они работали?

Вот скриншот настройки конфигурации для проекта: enter image description here

Ответы

Ответ 1

Короткий ответ - сервер dev - это 32-битный процесс.

Длинный ответ для "почему всего 256 Мб?"

Прежде всего, дайте понять, как это работает.

MemoryStream имеет внутренний буфер [], чтобы сохранить все данные. Он не может предсказать точный размер этого буфера, поэтому он просто инициализирует его с некоторым начальным значением.

Свойства позиции и длины не отражают фактический размер буфера - они являются логическими значениями, отражающими количество байтов и легко могут быть меньше фактического размера физического буфера.

Если этот внутренний буфер не может вместить все данные, он должен быть "изменен", но в реальной жизни это означает создание нового буфера в два раза по размеру предыдущего, а затем копирование данных от старого буфера до нового буфера.

Итак, если длина вашего буфера составляет 256 Мб, и вам нужны новые данные, которые нужно записать, это значит, что .Net нужно найти еще один блок данных 512 МБ - все остальное на месте, поэтому куча должна быть в не менее 768 Мб в момент выделения памяти при получении OutOfMemory.

Также обратите внимание, что по умолчанию ни один объект, включая массивы, в .Net не может иметь размер более 2 ГБ.

Итак, вот образец, который имитирует происходящее:

        byte[] buf = new byte[32768 - 10];

        for (; ; )
        {
            long newSize = (long)buf.Length * 2;
            Console.WriteLine(newSize);

            if (newSize > int.MaxValue)
            {
                Console.WriteLine("Now we reach the max 2Gb per single object, stopping");
                break;
            }

            var newbuf = new byte[newSize];
            Array.Copy(buf, newbuf, buf.Length);
            buf = newbuf;
        }

Если он построен в x64/AnyCPU и запускается с консоли - все в порядке.

Если он построен на x86 - он не работает в консоли.

Если вы положили его на страницу "Page_Load", встроенную в x64 и открытую с веб-сервера VS.Net, это не сработает.

Если вы делаете то же самое с IIS - все в порядке.

Надеюсь, что это поможет.

Ответ 2

Если вы используете сервер разработки VS по умолчанию, вы запускаете код в процессе x86/32 бит. Если вы используете полный IIS - скорее всего, в IIS, в частности, AppPool настроен на работу в x86 (32-разрядный режим) и, как результат, имеет очень ограниченное адресное пространство (2 ГБ, если только вы не отметили ваше приложение как A Large Aware).

В случае IIS убедитесь, что вы настроили опросы пользователей для запуска x64 (не уверены, что такое значение по умолчанию). Убедитесь, что ваш целевой код установлен на AnyCPU или x64.

Для автономных приложений С# - по умолчанию они скомпилированы с x86 или AnyCPU/Prefer x86 - смените целевую платформу на x64.

Чтобы получить поддержку x64 для IIS, вы можете установить полный IIS или установить IIS Express 8.0 (7.5, который поставляется с Windows 7, 32 бит только) из Загрузить IIS 8.0 Express.

Боковые заметки: