Java: достаточно свободной кучи для создания объекта? - PullRequest
7 голосов
/ 01 декабря 2008

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

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 или какая-то идея управления памятью ниже уровня абстракции самого языка?

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

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

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

Ответы [ 5 ]

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

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

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

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

- запуск не гарантирован немедленно

- он остановит все на своем пути, пока он работает

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

2 голосов
/ 01 декабря 2008

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

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

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

2 голосов
/ 01 декабря 2008

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

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

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

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

2 голосов
/ 01 декабря 2008

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

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

Единственная альтернатива, которую я вижу, - это использовать жесткий диск в качестве памяти (RAM / ROM, как в прежние времена). Я думаю, это то, на что вы указываете в своем «еще более медленном, менее требовательном подходе»

У каждой платформы есть свои ограничения, поддержка java столько, сколько ОЗУ готово предоставить вашему оборудованию (ну, на самом деле, вы настраиваете виртуальную машину) В Sun JVM подразумевается, что это можно сделать с помощью

-Xmx 

Опция

как

java -Xmx8g some.name.YourMemConsumingApp

Например,

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

Если это ваш случай, вам непременно следует перейти на диск.

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

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

 strategy.performTheAction();

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

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

: P

РЕДАКТИРОВАТЬ

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

Предположим, это теоретическая проблема.

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

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

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

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

 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 голосов
/ 01 декабря 2008

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

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

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

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

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