Является ли "Недостаточно памяти" исправимой ошибкой? - PullRequest
75 голосов
/ 02 декабря 2008

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

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

Какой веский аргумент в пользу того, чтобы сделать его исправимой ошибкой?

Ответы [ 23 ]

36 голосов
/ 02 декабря 2008

Это действительно зависит от того, что вы строите.

Для веб-сервера весьма небезосновательно проваливать одну пару запрос / ответ, но затем продолжать отправлять дальнейшие запросы. Тем не менее, вы должны быть уверены, что один сбой не оказал пагубного влияния на глобальное состояние, однако это было бы непросто. Учитывая, что сбой вызывает исключение в большинстве управляемых сред (например, .NET и Java), я подозреваю, что если исключение будет обработано в «коде пользователя», его можно будет восстановить для будущих запросов - например, если один запрос попытался выделить 10 ГБ памяти и потерпел неудачу, это не должно повредить остальной системе. Однако если системе не хватает памяти при попытке передать запрос пользовательскому коду, такие вещи могут быть более неприятными.

17 голосов
/ 02 декабря 2008

В библиотеке вы хотите эффективно скопировать файл. Когда вы делаете это, вы обычно обнаруживаете, что копирование с использованием небольшого количества больших чанков гораздо эффективнее, чем копирование множества меньших (скажем, быстрее скопировать файл размером 15 МБ, скопировав 15 кусков размером 1 МБ, чем копировать 15 000). 1K кусков).

Но код работает с любым размером чанка. Поэтому, хотя это может быть быстрее при использовании блоков размером 1 МБ, если вы разрабатываете систему, в которую копируется большое количество файлов, может быть целесообразно перехватить OutOfMemoryError и уменьшать размер фрагмента до тех пор, пока вы не добьетесь успеха.

Другое место - это кэш для объекта, хранящийся в базе данных. Вы хотите сохранить как можно больше объектов в кэше, но не хотите мешать остальной части приложения. Поскольку эти объекты можно воссоздать, это разумный способ сэкономить память, чтобы прикрепить кэш к обработчику нехватки памяти, чтобы отбрасывать записи до тех пор, пока у остальной части приложения снова не будет достаточно места для дыхания.

Наконец, для манипулирования изображениями вы хотите загрузить как можно больше изображения в память. Опять же, обработчик OOM позволяет вам реализовать это, не зная заранее, сколько памяти пользователь или ОС предоставит вашему коду.

[ПРАВКА] Обратите внимание, что я работаю в предположении, что вы дали приложению фиксированный объем памяти, и этот объем меньше, чем общий объем доступной памяти, исключая пространство подкачки. Если вы можете выделить столько памяти, что часть ее должна быть заменена, некоторые из моих комментариев больше не имеют смысла.

9 голосов
/ 02 декабря 2008

Пользователям MATLAB все время не хватает памяти при выполнении арифметических операций с большими массивами. Например, если переменная x помещается в памяти и выполняется «x + 1», тогда MATLAB выделяет место для результата и затем заполняет его. Если при распределении происходит ошибка MATLAB, и пользователь может попробовать что-то еще. Было бы катастрофой, если бы MATLAB выходил всякий раз, когда появлялся этот вариант использования.

8 голосов
/ 04 января 2009

OOM должно быть восстановимо, потому что завершение работы - не единственная стратегия восстановления после OOM.

На самом деле существует довольно стандартное решение проблемы ООМ на уровне приложений. При разработке приложения определите минимальный безопасный объем памяти, необходимый для восстановления из-за нехватки памяти. (Например, память, необходимая для автоматического сохранения документов, отображения диалоговых окон с предупреждениями, регистрации данных отключения).

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

Обратите внимание, что приложение не нужно закрывать. Он может отображать модальное диалоговое окно, пока условие OOM не будет разрешено.

Я не уверен на 100%, но я уверен, что ' Code Complete ' (требуется чтение для любого уважаемого инженера-программиста) покрывает это.

P.S. Вы можете расширить свою прикладную среду, чтобы помочь с этой стратегией, но, пожалуйста, не реализуйте такую ​​политику в библиотеке (хорошие библиотеки не принимают глобальные решения без согласия приложений)

5 голосов
/ 02 декабря 2008

Я работаю в системе, которая выделяет память для кэша ввода-вывода для повышения производительности. Затем, обнаружив OOM, он забирает некоторую часть обратно, чтобы бизнес-логика могла продолжаться, даже если это означает меньший объем кэша ввода-вывода и немного меньшую производительность записи.

Я также работал со встроенными Java-приложениями, которые пытались управлять OOM, форсируя сборку мусора, опционально освобождая некоторые некритические объекты, такие как предварительно извлеченные или кэшированные данные.

Основные проблемы с обработкой OOM:

1) возможность повторной попытки в том месте, где это произошло, или возможность откатиться и повторить попытку с более высокой точки. Большинство современных программ слишком сильно зависят от языка и не могут точно определить, где они находятся, и как повторить операцию. Обычно контекст операции теряется, если он не предназначен для сохранения

2) возможность освободить память. Это означает своего рода менеджер ресурсов, который знает, какие объекты являются критическими, а какие нет, и система сможет повторно запросить освобожденные объекты, когда и если они позже станут критическими

Еще одна важная проблема - возможность откатиться, не вызывая еще одну ситуацию с OOM. Это то, что трудно контролировать в языках более высокого уровня.

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

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

Из-за всего этого часто большие и встраиваемые системы используют эти методы, поскольку они имеют контроль над ОС и памятью, чтобы позволить им, и дисциплину / мотивацию для их реализации.

5 голосов
/ 02 декабря 2008

Я думаю, что, как и многие вещи, это анализ затрат / выгод. Вы можете запрограммировать попытку восстановления после сбоя malloc () - хотя это может быть сложно (ваш обработчик не должен игнорировать тот же недостаток памяти, с которым он должен был справиться).

Вы уже заметили, что наиболее распространенным случаем является очистка и изящный сбой. В этом случае было решено, что стоимость прерывания изящно ниже, чем сочетание затрат на разработку и затрат на производительность при восстановлении.

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

4 голосов
/ 02 декабря 2008

Восстанавливаемый, только если вы поймаете его и правильно с ним справитесь.

В тех же случаях, например, запрос пытался выделить много памяти. Это вполне предсказуемо, и вы можете справиться с этим очень хорошо.

Однако во многих случаях в многопоточном приложении OOE также может происходить в фоновом потоке (в том числе создаваемом системной / сторонней библиотекой). Это почти невозможно предсказать, и вы не сможете восстановить состояние всех ваших потоков.

3 голосов
/ 02 декабря 2008

Нет. Ошибка нехватки памяти от GC, как правило, не может быть исправлена ​​внутри текущего потока. (Создание и завершение восстанавливаемого потока (пользователя или ядра) должно поддерживаться, хотя)

Что касается примеров счетчиков: в настоящее время я работаю над проектом на языке программирования D, который использует платформу NVIDIA CUDA для вычислений на GPU. Вместо того, чтобы вручную управлять памятью GPU, я создал прокси-объекты, чтобы использовать DC GC. Поэтому, когда графический процессор возвращает ошибку «Недостаточно памяти», я запускаю полный сбор данных и выдаю исключение только в случае сбоя во второй раз. Но на самом деле это не пример восстановления из нехватки памяти, это скорее интеграция с GC. Другими примерами восстановления (кэши, свободные списки, стеки / хэши без автоматического сжатия и т. Д.) Являются все структуры, которые имеют свои собственные методы сбора / сжатия памяти, которые отделены от ГХ и не являются локальными по отношению к распределению. функция. Чтобы люди могли реализовать что-то вроде следующего:

T new2(T)( lazy T old_new ) {
    T obj;
    try{
        obj = old_new;
    }catch(OutOfMemoryException oome) {
        foreach(compact; Global_List_Of_Delegates_From_Compatible_Objects)
            compact();
        obj = old_new;
    }
    return obj;
}

Что является достойным аргументом для добавления поддержки регистрации / отмены регистрации самосборных / уплотняющих объектов для сборщиков мусора в целом.

1 голос
/ 02 декабря 2008

Это зависит от того, что вы подразумеваете под нехваткой памяти.

Когда на большинстве систем происходит сбой malloc(), это происходит из-за того, что вы исчерпали адресное пространство.

Если большая часть этой памяти используется для кэширования или для областей mmap, вы можете восстановить часть этой памяти, освободив кэш или отключив его. Однако для этого действительно необходимо, чтобы вы знали, для чего вы используете эту память, и, как вы заметили, большинство программ этого не делают, или это не имеет значения.

Если вы использовали setrlimit() на себе (для защиты от непредвиденных атак, возможно, или, может быть, root сделал это с вами), вы можете ослабить ограничение в вашем обработчике ошибок. Я делаю это очень часто - после запроса пользователя, если это возможно, и регистрации события.

С другой стороны, отлов переполнения стека немного сложнее и не переносим. Я написал подходящее решение для ECL и описал реализацию Windows, если вы идете по этому пути. Это было проверено в ECL несколько месяцев назад, но я могу выкопать оригинальные патчи, если вам интересно.

1 голос
/ 05 августа 2013

Вопрос помечен как «независимый от языка», но на него трудно ответить без учета языка и / или базовой системы. (Я вижу несколько хижин toher

Если выделение памяти является неявным, без механизма определения того, было ли данное выделение выполнено успешно или нет, то восстановление из состояния нехватки памяти может быть трудным или невозможным.

Например, если вы вызываете функцию, которая пытается выделить огромный массив, большинство языков просто не определяют поведение, если массив не может быть выделен. (В Аде это вызывает исключение Storage_Error, по крайней мере, в принципе, и это должно быть возможно обработать.)

С другой стороны, если у вас есть механизм, который пытается выделить память и может сообщить о сбое при этом (например, C malloc() или C ++ new), то да, конечно, можно восстановить из этот провал. По крайней мере, в случаях malloc() и new неудачное выделение не делает ничего, кроме сообщения о сбое (например, не повреждает никакие внутренние структуры данных).

Имеет ли смысл попытаться восстановить, зависит от приложения. Если приложение просто не может быть успешным после сбоя выделения, тогда оно должно выполнить любую возможную очистку и завершить работу. Но если ошибка выделения означает лишь то, что одна конкретная задача не может быть выполнена или если задача все еще может выполняться медленнее с меньшим объемом памяти, то имеет смысл продолжить работу.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...