Является ли "Из памяти" восстановимой ошибкой?

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

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

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

Ответ 1

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

Ответ 4

OOM следует восстанавливать, потому что выключение не является единственной стратегией восстановления из OOM.

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

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

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

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

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

Ответ 5

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

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

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

Ответ 6

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

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

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

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

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

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

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

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

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

Ответ 7

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

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

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

Ответ 8

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

Что касается примеров счетчиков: в настоящее время я работаю над проектом языка программирования D, который использует платформу NVIDIA CUDA для вычисления графических процессоров. Вместо ручного управления памятью GPU я создал объекты прокси, чтобы использовать D GC. Поэтому, когда GPU возвращает ошибку из памяти, я запускаю полный сбор и только возбуждаю исключение, если он не работает второй раз. Но на самом деле это не пример восстановления памяти, это скорее интеграция 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;
}

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

Ответ 9

В общем случае он не восстанавливается.

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

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

Ответ 10

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

Когда malloc() не работает в большинстве систем, это связано с тем, что у вас закончилось адресное пространство.

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

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

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

Ответ 11

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

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

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

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

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

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

Ответ 12

Это сложный вопрос. На первый взгляд кажется, что больше нет памяти означает "из удачи", но вы также должны видеть, что можно избавиться от многих связанных с памятью вещей, если вы действительно настаиваете. Пусть просто возьмем другим способом сломанную функцию strtok, которая, с одной стороны, не имеет проблем с памятью. Затем возьмите в качестве аналога g_string_split из библиотеки Glib, что в значительной степени зависит от распределения памяти как почти всего в glib или программах на основе GObject. Можно сказать, что в более динамичных языках выделение памяти гораздо больше используется, как в более негибких языках, особенно в C. Но рассмотрим альтернативы. Если вы закончите программу, если у вас закончилась нехватка памяти, даже тщательно разработанный код может перестать работать. Но если у вас есть исправляемая ошибка, вы можете что-то сделать. Таким образом, аргумент, позволяющий восстановить его, означает, что можно "по-разному" обрабатывать эту ситуацию (например, откладывать блок памяти для чрезвычайных ситуаций или деградировать в более обширную программу с меньшим объемом памяти).

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

Привет

Ответ 13

Теперь меня просто озадачивает.

На работе у нас есть пакет приложений, работающих вместе, а память работает на низком уровне. Хотя проблема заключается в том, чтобы сделать пакет приложений 64-разрядным (и, таким образом, иметь возможность работать за пределы 2 Go, которые мы имеем на нормальной ОС Win32) и/или уменьшить использование памяти, эта проблема "Как сделать оправиться от OOM" не уйдет с моей головы.

Конечно, у меня нет решения, но все равно играю в поисках одного для С++ (из-за RAII и исключений, в основном).

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

Если одна из задач выходит из строя, С++ bad_alloc развяжет стек, освободит некоторую стек/кучу памяти через RAII. Восстановительная функция затем спасла бы как можно больше (сохранение исходных данных задачи на диске, использование при последующей попытке) и, возможно, зарегистрировать данные задачи для более поздней попытки.

Я действительно считаю, что использование С++ strong/nothrow guanrantees может помочь процессу выжить в условиях с низкой доступной памятью, даже если это будет сродная замена памяти (т.е. медленная, несколько некорректная и т.д.), но, конечно, это только теория. Мне просто нужно усвоить эту тему, прежде чем пытаться имитировать это (т.е. Создать программу на С++ с помощью специального диспетчера добавления/удаления с ограниченной памятью, а затем попытаться выполнить некоторую работу в этих стрессовых условиях).

Ну...

Ответ 14

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

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

Ответ 15

Да, OOM можно восстановить. В качестве крайнего примера, операционные системы Unix и Windows довольно часто восстанавливаются из условий OOM, большую часть времени. Приложения терпят неудачу, но ОС выживает (при условии, что для нормальной работы ОС достаточно памяти).

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

Проблема работы с OOM действительно зависит от вашей программы и среды.

Например, во многих случаях место, где OOM происходит, скорее всего, НЕ является лучшим местом для фактического восстановления из состояния OOM.

Теперь пользовательский распределитель может работать как центральная точка внутри кода, который может обрабатывать OOM. Java-распределитель будет выполнять полный GC до того, как на самом деле выбрасывает исключение OOM.

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

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

Итак, да, OOM можно восстановить, но это может быть очень сложно, особенно в современных средах, таких как Java, и множество разнообразных сторонних библиотек различного качества.

Ответ 16

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

Ответ 17

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

Ответ 18

Здесь уже много хороших ответов. Но я хотел бы внести свой вклад с другой точки зрения.

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

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

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

Мои два цента: -)

Ответ 19

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

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

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

Ответ 20

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

Я не вижу в любом случае этого в многопоточном приложении. Как вы знаете, какой поток фактически отвечает за ошибку вне памяти? Один поток мог постоянно выделять новую память и иметь gc-корни до 99% кучи, но первое выделение, которое не выполняется, происходит в другом потоке.

Практический пример: всякий раз, когда я встречал OutOfMemoryError в нашем приложении Java (работает на сервере JBoss), он не похож на один поток, и остальная часть сервера продолжает работать: нет, есть несколько OOME, потоки (некоторые из которых - внутренние потоки JBoss). Я не понимаю, что я, как программист мог сделать, чтобы оправиться от этого - или даже то, что JBoss мог сделать, чтобы оправиться от него. На самом деле, я даже не уверен, что вы МОЖЕТЕ: javadoc для VirtualMachineError предполагает, что JVM может быть "сломан" после того, как эта ошибка будет выброшена, Но, возможно, вопрос был более ориентирован на дизайн языка.

Ответ 21

uClibc имеет внутренний статический буфер объемом 8 байтов или около того для ввода-вывода файлов, когда больше не требуется динамически выделять память.

Ответ 22

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

В Java убедительный аргумент в том, что он не делает его восстановимой ошибкой, заключается в том, что Java позволяет сигнализировать OOM в любое время, в том числе время от времени, когда результатом может быть ваша программа, вступающая в несогласованное состояние. Поэтому надежная рекуперация из OOM невозможна; если вы поймаете исключение OOM, вы не можете полагаться на какое-либо ваше состояние программы. Видеть Предотвращение отказов VirtualMachineError

Ответ 23

У меня есть это:

void *smalloc(size_t size) {
  void *mem = null; 
  for(;;) {
   mem = malloc(size);
   if(mem == NULL) {
    sleep(1);
   } else 
     break;
  }
  return mem;
}

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