Java: достаточно свободной "кучи" для создания объекта?

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

try {
    // try to perform an operation using a huge in-memory array
    byte[] massiveArray = new byte[BIG_NUMBER];
} catch (OutOfMemoryError oome) {
    // perform the operation in some slower but less
    // memory intensive way...
}

Это не кажется правильным, так как Sun самостоятельно рекомендует, чтобы Вы не должны были пытаться поймать Error или его подклассы. Мы обсудили это, и другая идея, которая подошла, явно проверяла на свободную "кучу":

if (Runtime.getRuntime().freeMemory() > SOME_MEMORY) {
    // quick memory-intensive approach
} else {
    // slower, less demanding approach
}

Снова, это кажется неудовлетворительным - особенно в том выборе значения для SOME_MEMORY является трудным легко коснуться рассматриваемого задания: для некоторого произвольного большого объекта, как я могу оценить, в каком количестве, возможно, нуждалась бы память его инстанцирование?

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

Редактирование 1: в первом примере могло бы на самом деле быть выполнимо оценить объем памяти a byte[] из данной длины мог бы занять, но там более универсальный путь, который расширяется на произвольные большие объекты?

Редактирование 2: как @erickson указывает, существуют способы оценить размер объекта, после того как он создается, но (игнорирование статистического подхода на основе предыдущих размеров объекта) там способ сделать так для все же несозданных объектов?

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

7
задан deHaar 1 September 2019 в 20:09
поделиться

5 ответов

freeMemory не совершенно правилен. Необходимо было бы также добавить maxMemory ()-totalMemory (). например, принятие Вас запускает VM с max-memory=100M, май JVM во время Вашего вызова метода только использовать (от ОС) 50M. Из этого, скажем, 30M на самом деле используется JVM. Это означает, что Вы покажете 20M свободный (примерно, потому что мы только говорим о "куче" здесь), но при попытке сделать свой больший объект, то она попытается захватить другой 50M, его контракт позволяет ей брать от ОС перед отказом и erroring. Таким образом, Вы на самом деле (теоретически) имели бы 70M доступный.

Для создания этого более сложным 30M, это сообщает, поскольку используемый в вышеупомянутом примере включает материал, который может иметь право на сборку "мусора". Таким образом, можно на самом деле иметь больше памяти в наличии, если она придет в ярость, то она попытается выполнить GC для освобождения большей памяти.

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

- это, как гарантируют, не будет сразу работать

- это остановит все в своих дорожках, в то время как это работает

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

5
ответ дан 6 December 2019 в 19:43
поделиться

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

Однако в этом случае я думаю, что могло бы быть лучше зафиксировать Ошибку. В первую очередь, просьба о свободной памяти не составляет то, что доступно после сборки "мусора", которая будет выполнена прежде, чем повысить OOME. И, запрашивая сборку "мусора" с System.gc() не надежно. Это часто явно отключается, потому что это может разрушить производительность, и если это не отключило … хорошо, это может разрушить производительность при использовании излишне.

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

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

2
ответ дан 6 December 2019 в 19:43
поделиться

"Попытка выделить и обработать ошибку" подход очень опасна.

  • Что, если Вы едва получаете свою память? Более позднее исключение OOM могло бы произойти, потому что Вы принесли вещи, слишком близкие к пределам. Почти любой вызов библиотеки выделит память по крайней мере кратко.
  • Во время Вашего выделения другой поток может получить исключение OOM при попытке выделить относительно маленький объект. Даже если Ваше выделение предназначено для сбоя.

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

2
ответ дан 6 December 2019 в 19:43
поделиться

Я не полагаю, что существует разумный, универсальный подход к этому, которое, как могло безопасно предполагаться, было на 100% надежно. Даже подход Runtime.freeMemory уязвим для того, что у Вас может на самом деле быть достаточно памяти после сборки "мусора", но Вы не знали бы, что, если Вы не вынуждаете gc, но затем нет никакого надежного способа вызвать GC также.:)

Однако я подозреваю, знали ли Вы действительно приблизительно, в каком количестве Вы нуждались и действительно выполняли a System.gc() заранее, и Ваше выполнение в простом однопоточном приложении, у Вас был бы довольно достойный выстрел в разбирание в нем с вызовом .freeMemory.

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

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

2
ответ дан 6 December 2019 в 19:43
поделиться

Определенно фиксация ошибки является худшим подходом. Ошибка происходит, когда нет НИЧЕГО, что можно делать с этим. Даже не создайте журнал, затяжку, как "... Хьюстон, мы потеряли VM".

Я не вполне получил вторую причину. Это было плохо, потому что трудно связать SOME_MEMORY с операциями? Вы могли перефразировать его для меня?

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

Каждая платформа имеет свои пределы, поддержка Java так же как RAM, которую Ваши аппаратные средства готовы дать (хорошо на самом деле Вы путем конфигурирования VM) В Sun JVM impl, который мог быть, покончила

-Xmx 

Опция

как

java -Xmx8g some.name.YourMemConsumingApp

Например,

Конечно, можно закончить тем, что пытались выполнить операцию, которая берет 10 ГБ RAM

Если это - Ваш случай затем, необходимо определенно подкачать к диску.

Кроме того, использование стратегической модели могло сделать более хороший код. Хотя здесь это смотрит излишество:

if (isEnoughMemory(SOME_MEMORY)) {
    strategy = new InMemoryStrategy();
} else {
    strategy = new DiskStrategy();
}

strategy.performTheAction();

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

...
strategy = new ImaginaryCloudComputingStrategy();
...

:P

Править

После получения проблемы со вторым подходом: Если существует несколько раз, когда Вы не знаете, сколько RAM будет использованной, но Вы действительно знаете, сколько Вы оставили, Вы могли использовать смешанный подход (RAM, когда у Вас есть достаточно, ROM [диск], когда Вы не делаете),

Предположим эта theorical проблема.

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

Затем Вы выполняете, некоторая операция на том потоке (зашифруйте его, например).

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

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

while( !isDone() ) {
    if (isMemoryLow()) {
        //Runtime.getRuntime().freeMemory() < SOME_MEMORY + some other validations 
        swapToDisk(); // and make sure resources are GC'able
    }

    byte [] array new byte[PREDEFINED_BUFFER_SIZE];
    process( array );

    process( array );
}

cleanUp();
2
ответ дан 6 December 2019 в 19:43
поделиться
Другие вопросы по тегам:

Похожие вопросы: