Должны ли мы избегать создания объектов на Java?
Мне сказал коллега, что создание объектов Java - это самая дорогая операция, которую вы могли бы выполнить. Поэтому я могу заключить только, чтобы создать как можно меньше объектов.
Кажется, это несколько портит цель объектно-ориентированного программирования. Если мы не создаем объекты, мы просто пишем один длинный класс C для оптимизации?
13 ответов
Ваш коллега понятия не имеет, о чем они говорят.
Ваша самая дорогая операция будет их слушать . Они потратили ваше время на неправильное направление информации на более чем десятилетнюю дату (начиная с первоначальной даты, когда этот ответ был отправлен) , а также вам приходится тратить время на размещение здесь и исследовать Интернет за правду.
Надеюсь, они просто неохотно срывают то, что они слышали или читают более десяти лет назад, и не знают ничего лучшего. Я бы взял что-нибудь еще, что они говорят как подозреваемые, это должно быть хорошо известная ошибка любого, кто постоянно обновляется в любом случае.
Все есть объект (кроме примитивов
)
Все, кроме примитивов (int, long, double
и т. д.), являются объектами в Java. Невозможно избежать создания объектов на Java.
В начале 1990-х годов в начале 2000-х годов реализации JVM имели некоторые накладные расходы на производительность при фактическом распределении объектов. Это было не так с 2005 года.
Если вы настроите -Xms
, чтобы поддерживать всю память, необходимую для правильной работы вашего приложения, GC, возможно, никогда не придется запускать и перемещать большую часть мусора в современных реализациях GC, программы могут вообще не GC.
Он не пытается и максимизирует свободное пространство, которое является красной серой, так или иначе, оно максимизирует производительность среды выполнения. Если это означает, что куча JVM Heap почти 100% распределена все время, пусть будет так. Свободная куча памяти JVM не дает вам ничего, кроме как сидеть там.
Существует неправильное представление о том, что GC освободит память обратно в остальную часть системы полезным способом, это абсолютно неверно!
Куча JVM не растет и не уменьшается, поэтому на остальную часть системы положительно влияет свободная память в JVM Heap . -Xms
выделяет ВСЕ то, что указано при запуске, и его эвристика - это никогда не выпускать какую-либо из этой памяти обратно в ОС для совместного использования с любыми другими процессами ОС до тех пор, пока этот экземпляр JVM не завершится полностью. -Xms = 1GB -Xmx = 1GB
выделяет 1 ГБ ОЗУ независимо от того, сколько объектов фактически создано в любой момент времени. Существуют некоторые настройки, позволяющие освобождать проценты памяти кучи, но для < сильные> все практические цели JVM никогда не сможет выпустить достаточное количество этой памяти, чтобы это никогда не происходило , поэтому никакие другие процессы не могут вернуть эту память, поэтому остальная часть системы не использует JVM Heap также свободен. RFE для этого было «принято» 29-NOV-2006, но о нем ничего не было сделано. Это поведение не считается проблемой любого из авторитетов.
Существует неправильное представление о том, что создание множества небольших короткоживущих объектов заставляет JVM приостанавливаться в течение длительных периодов времени, теперь это также ложно.
Современные алгоритмы GC на самом деле оптимизированы для создания многих небольших объектов, которые недолговечны, что в основном 99% -ое эвристическое для объектов Java в каждой программе. Попытки Объединение объектов фактически сделают JVM хуже в большинстве случаев.
Единственными объектами, которые сегодня нужны для объединения, являются объекты, которые ссылаются на конечные ресурсы, которые внешние для JVM; Сокеты, файлы, подключения к базам данных и т. Д. И могут быть повторно использованы. Обычные объекты не могут быть объединены в том же смысле, что и на языках, которые позволяют вам напрямую обращаться к ячейкам памяти. Объект кеширование - это другое понятие и может быть или не быть тем, что некоторые люди наивно называют пулом , эти два понятия не одно и то же, и их не следует связывать.
Современные алгоритмы GC не имеют этой проблемы, потому что они не освобождаются от расписания, они освобождаются, когда требуется свободная память в определенном поколении. Если куча достаточно велика, то никаких освобождений не происходит достаточно долго, чтобы вызвать паузы.
Нижняя линия: Не компрометируйте свой дизайн, чтобы создавать ярлыки для создания объектов. Не допускайте ненужного создания объектов. Если разумный, дизайн, чтобы избежать избыточных операций (любого рода).
В отличие от большинства ответов - да, распределение объектов У них есть связанная с этим стоимость. Это низкая стоимость, но вы должны избегать создания ненужных объектов. То же самое, что вам следует избегать ненужного в вашем коде. Графики больших объектов делают GC более медленным, подразумевая более длительное время выполнения, в котором вы, вероятно, будете иметь больше вызовов методов, вызывать больше промахов в кеш-памяти и повышать вероятность того, что ваш процесс будет заменен на диск в случаях с низкой оперативной памятью.
Прежде чем кто-нибудь из насчет того, чтобы это было краевым случаем, у меня есть профилированные приложения, которые перед оптимизацией создали 20 + МБ объектов для обработки ~ 50 строк данных. Это нормально при тестировании, пока вы не увеличите до сотни запросов в минуту, и вдруг вы создаете 2 ГБ данных в минуту. Если вы хотите сделать 20 запросов /сек, вы создаете 400 МБ объектов, а затем выбрасываете их. 20 reqs /sec является крошечным для достойного сервера.
На самом деле, из-за стратегий управления памятью, которые делают язык Java (или любой другой управляемый язык), создание объекта - это нечто большее, чем увеличение указателя в блоке памяти, называемом молодым поколением. Это намного быстрее, чем C, где нужно искать свободную память.
Другая часть стоимости - уничтожение объекта, но с C. трудно сравнивать C. Стоимость коллекции основана на количестве объектов, сохраненных в долгосрочной перспективе, но частота коллекций основана на количестве созданных объектов. .. в конце концов, он все еще намного быстрее, чем управление памятью в стиле C.
Другие плакаты справедливо отметили, что создание объектов на Java очень быстро, и вы не должны беспокоиться об этом в любом обычном Java-приложении.
Есть пара особых ситуаций, когда есть хорошая идея избежать создания объекта.
- Когда вы пишете приложение, чувствительное к задержкам, и хотите избежать пауз GC. Чем больше объектов вы производите, тем больше происходит GC и тем больше вероятность пауз. Это может быть актуальным для релевантных игр, некоторых медиа-приложений, роботизированного управления, высокочастотной торговли и т. Д. Решение состоит в том, чтобы предварительно выделить все объекты /массивы, которые вам нужны, и повторно использовать их. Существуют библиотеки, которые специализируются на предоставлении такого рода возможностей, например Javolution . Но, возможно, если вы действительно заботитесь о низкой задержке, вы должны использовать C /C ++ /ассемблер, а не Java или C #: -)
- Избегание примитивов в штучной упаковке (Double, Integer и т. д.) может быть очень полезной микро-оптимизацией при определенных обстоятельствах. Поскольку unboxed primitives (double, int и т. Д.) Избегают накладных расходов для каждого объекта, они намного быстрее работают с интенсивным процессором, например, с числовой обработкой и т. Д. Обычно примитивные массивы отлично работают на Java, поэтому вы хотите использовать их для вашего хруста, а не любые другие виды объектов.
- В ситуациях с ограниченной памятью вы хотите свести к минимуму количество (активных) объектов, созданных, поскольку каждый объект несет небольшие накладные расходы (обычно 8-16 байтов в зависимости от реализации JVM). В таких обстоятельствах вы должны предпочесть небольшое количество больших объектов или массивов для хранения ваших данных, а не большого количества небольших объектов.
Есть ядро истины в том, что говорит ваш коллега. Я с уважением предлагаю проблему с созданием объекта creation на самом деле является сборкой коллекции . В C ++ программист может точно контролировать, как освобождается память. Программа может накапливать crud как можно дольше или короче, чем нравится. Кроме того, программа на C ++ может отменить crud, используя другой поток, чем тот, который его создал. Таким образом, поток, который в настоящее время работает, никогда не должен останавливаться, чтобы очистить.
Напротив, виртуальная машина Java (JVM) периодически останавливает ваш код для восстановления неиспользуемой памяти. Большинство разработчиков Java никогда не замечают эту паузу, поскольку она обычно бывает нечастой и очень короткой. Чем больше вы накапливаете или чем более ограничено ваша JVM, тем чаще происходят эти паузы. Для визуализации этого процесса вы можете использовать такие инструменты, как VisualVM .
В последних версиях Java алгоритм сборки мусора (GC) можно настроить . Как правило, чем короче вы хотите сделать паузы, тем дороже накладные расходы на виртуальной машине (т. Е. Процессор и память потрачены на координацию процесса GC).
Когда это может произойти? В любое время, когда вы заботитесь о последовательных частотах ответа в миллисекундах, вы будете заботиться о GC. Автоматизированные торговые системы, написанные на Java, настраивают JVM для минимизации пауз. Фирмы, которые в противном случае пишут Java, превращаются в C ++ в ситуациях, когда системы должны быть очень отзывчивыми все время.
Для записи, я делаю не оправдывать обход объекта вообще! По умолчанию объектно-ориентированное программирование. Отрегулируйте этот подход только в том случае, если GC окажется на вашем пути, а затем только после попытки настроить JVM на паузу в течение меньшего времени. Хорошей книгой по настройке производительности Java является производительность Java от Charlie Hunt и Binu John.
Существует один случай, когда вам может быть не рекомендуется создавать слишком много объектов на Java из-за накладных расходов - проектирование производительности на платформе Android
Кроме того, эти ответы верны.
GC настраивается для многих коротких объектов
, который сказал, что если вы можете с минимальным уменьшением размещения объектов, вы должны
В одном примере можно было бы построить строку в цикле, наивный способ был бы
String str = "";
в то время как (someCondition) {
//...
str + = appendingString;
}
, который создает новый объект String
для каждой операции + =
(плюс StringBuilder
и новый базовый массив символов)
вы можете легко переписать это на:
StringBuilder strB = new StringBuilder ();
в то время как (someCondition) {
//...
strB.append (appendingString);
}
String str = strB.toString ();
этот шаблон (неизменный результат и локальное изменяемое промежуточное значение) может быть применен и к другим вещам.
, но кроме этого вы должны подтянуть профилировщик, чтобы найти реальное узкое место вместо того, чтобы преследовать призраков
Это действительно зависит от конкретного приложения, поэтому в целом сложно сказать. Тем не менее, я был бы очень удивлен, если создание объекта было фактически узким местом в приложении. Даже если они медленные, преимущество в стиле кода, вероятно, перевешивает производительность (если только это не заметно для пользователя)
В любом случае вы даже не должны беспокоиться об этих вещах, пока не профилируете свой код, чтобы определить фактические узкие места производительности, а не гадать. До тех пор вы должны делать все, что лучше для чтения кода, а не производительности.
Джошуа Блох (один из разработчиков Java-платформы) написал в своей книге Эффективная Java в 2001 году:
Избегать создания объекта, поддерживая свой собственный пул объектов, является плохим если объекты в пуле чрезвычайно тяжелы. прототипическим примером объекта, который оправдывает пул объектов, является соединение с базой данных. Стоимость установления соединения достаточно высок, что имеет смысл повторно использовать эти объекты. Вообще говоря, однако, поддерживая собственные пулы объектов загромождает ваш код, увеличивает объем памяти и вредит представление. Современные реализации JVM имеют сильно оптимизированный мусор коллекционеров, которые легко превосходят такие пулы объектов на легком объекты.
Я думаю, что ваш коллега, должно быть, сказал с точки зрения создания ненужного объекта. Я имею в виду, что если вы часто создаете тот же объект, лучше делиться этим объектом. Даже в тех случаях, когда создание объекта является сложным и требует больше памяти, вы можете захотеть клонировать этот объект и не создавать этот сложный процесс создания объекта (но это зависит от вашего требования). Я думаю, что утверждение «Создание объекта дорого» должно быть принято в контексте.
Что касается требований к памяти JVM, дождитесь появления Java 8, вам даже не нужно указывать -Xmx, параметры метапространства будут заботиться о потребности в памяти JVM и будут автоматически расти.
Существует больше возможностей для создания класса, чем выделения памяти. Есть также инициализация, я не уверен, почему эта часть не охвачена всеми ответами. Нормальные классы содержат некоторые переменные и выполняют некоторую форму инициализации, и это не бесплатно. В зависимости от класса, он может читать файлы или делать любое другое количество медленных операций.
Итак, просто подумайте над тем, что делает конструктор класса, прежде чем считать, свободен он или нет.
Как утверждают люди, создание объектов не является большой стоимостью в Java (но я ставлю больше, чем большинство простых операций, таких как добавление и т. д.), и вы не должны слишком сильно его избегать.
Это говорит о том, что это по-прежнему стоит, и иногда вы можете попытаться обрезать как можно больше объектов. Но только после того, как профилирование показало, что это проблема.
Вот отличная презентация на тему: https://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf
Я сделал быстрый microbenchmark относительно этого, и я предоставили полные источники в github. Мое заключение заключается в том, что создание объектов дорого или нет, но это не проблема, но постоянно создавая объекты с понятием, что GC позаботится о вас, сделает ваше приложение более запущенным процессом GC. GC - очень дорогостоящий процесс, и лучше всего избегать его, когда это возможно, и не пытаться подтолкнуть его к нажатию.