Такому тонкому
Впрочем, существую и некоторые другие вариации как заставить garbage collector меньше мешать нам жить.
Что следует избегать
autoboxing
Тут скорее авторы java пошли на поводу у миллионов леммингов, орущих дайте мне сахара, да побольше, побольше!Чем плох autoboxing ? Самое очевидное, тем, что он создаёт overhead по памяти: так если long занимает 8 байт (причём на стеке, если это локальная переменная или аргумент метода), тогда как экземпляр java.lang.Long занимает уже 16 байт и всегда (~ почти всегда) на куче.
Теперь, о не очевидном: можно смело отбросить то, что будут вызываться методы для обращения к подлежащему примитиву (н-р метод longValue() у java.lang.Long) поскольку такие методы отлично inline'ятся.
Но прежде всего это создание нового узла для сборщика мусора - даже если экземпляр обёртки будет жить быстро и умрёт молодым, сборщику придётся немного потрудится, что потенциально может привести к ооочень незначительной остановке всего приложения.
Самое интересное во всём этом, то, что не только смерть молодых останавливает время, но и рождение может спровоцировать нечто подобное.
На это тонко и деликатно намекает зубр
Впрочем, что побочные эффекты при рождении и смерти объекта гарантированы каждому объекту и это касается далеко не только autoboxing, просто используя autoboxing вы это делаете неявно и в больших объёмах.
Но не стоит впадать в солипсизм - придерживайтесь здравого смысла - если использование autoboxing незначительное или вы используете значения из кэша значений -128 .. 127, то не стоит бросаться с топором и изничтожать его.
Если необходимы коллекции (в т.ч. и ассоциативные массивы) с использованием примитивов - Trove Collections - отличное решение, но стоит учитывать, что это не thread safe коллекции. Даже если вам не нужны коллекции с примитивами, всё равно стоит взглянуть на данный framework и некоторые детали реализации - open addressing, hashing strategies, особенно кто не слушал курсов по cs.
wrappers или объекты-обёртки
Пожалуй я даже почти забыл об этом, но давеча кандидат на интервью буквально расковырял мою старую фобию - wrapperовофию!Крайне нелепо плодить фантики-обёртки (и выкидывать) на критически важном (с точки зрения производительности) пути выполнения.
Словом, используя шаблоны проектирования включайте голову и ощущайте горький вкус.
String.format или MessageFormat.format
StringBuilder/StringBuffer в методах append использует default visibility методы на уровне пакета java.lang, такие как Integer.getChars(int value, int posAtBuf, char[] buf), которые дают возможность прямого добавления строкового представления числа в char[].
Для наглядности,
final String s = String.format("value is %d", ( 100000L + v) );
что творится внутри в деталях пообъектно:allocated the object of type java/nio/HeapCharBuffer whose size is 48 an array of java.lang.Object[1] allocated the object 100009 of type java/lang/Long whose size is 16 an array of char[16] allocated the object of type java/lang/StringBuilder whose size is 16 an array of char[18] allocated the object RU of type java/lang/StringBuffer whose size is 16 an array of char[3] allocated the string 'RUB' allocated the object java.text.DecimalFormatSymbols@117fc of type java/text/DecimalFormatSymbols whose size is 64 allocated the object of type java/util/Formatter whose size is 24 an array of java.lang.Object[10] allocated the object [] of type java/util/ArrayList whose size is 24 allocated the string 'value is ' allocated the string 'value is ' allocated the object value is of type java/util/Formatter$FixedString whose size is 16 an array of java.lang.String[6] allocated the string '' allocated the string 'd' an array of char[0] allocated the object of type java/util/Formatter$Flags whose size is 16 an array of java.util.Formatter$Flags[1] allocated the object d of type java/util/Formatter$FormatSpecifier whose size is 40 an array of java.util.Formatter$FormatString[0] an array of char[32] allocated the string 'java/util/Formatter$FormatString' an array of java.util.Formatter$FormatString[2] an array of char[16] allocated the object of type java/lang/StringBuilder whose size is 16 an array of char[6] allocated the string '100009' an array of char[6] an array of char[6] allocated the string '100009' an array of char[15] allocated the string 'value is 100009' String.format count:35 size:1016
против
final String s = "value is " + (100000L + v);
и его детали пообъектно:an array of char[25] allocated the object value is of type java/lang/StringBuilder whose size is 16 an array of char[15] allocated the string 'value is 100009' StringBuilder count:4 size:152
Словом, чего только нет в String.format - и уж сильно сомневаюсь, что escape анализ тут как-то поможет, в то же время как в случае неявного StringBuilder он может помочь избавив от одной аллокации массива (и копирования).
immutability
При всех своих плюсах immutability (нет необходимости в явных барьерах памяти, защищённость объекта и т.п.) не совместим с garbage less настолько, что добавить больше нечего.
Использовать с опаской
LinkedList
Стоит определится, что данная структура имеет скорее больше минусов, чем плюсов (по сравнению с ArrayList) - сложность поиска элемента по индексу O(N), дополнительные расходы по памяти и кроме того больше живых нод для gc, и как следствие, замедление его работы.
И пожалуй единственное разумное использование данной реализации списка, когда необходимо часто изменять - удалять или вставлять, элементы не в конец списка.
Кроме всего прочего, если сравнить подобные операции с ArrayList - то далеко не всегда удаление элемента даже из начала будет проигрышным для ArrayList - прежде всего из-за нелинейности при копировании памяти.
Улучшения
CopyOnWriteArrayList
При использовании шаблона observer, для многопоточного приложения, как правило, хранят список подписчиков в java.util.concurrent.CopyOnWriteArrayList, при разумном условии, что изменение списка происходит существенно реже, чем уведомление о событии.При обходе списка будет создан java.util.Iterator, который за свою короткую жизнь, успевает немного, но подгадить, особенно, если с учётом объёма потока сообщений.
Избежать это можно, если пойти на компромисс - нарушение безопасности в обмен на чистоту (garbage free):
Используемый нами CopyOnWriteArrayList отличается от базового java.util.concurrent.CopyOnWriteArrayList наличием метода
/**
* read-only access method
*/
public V[] getArray(){
return array;
}
Т.о. мы понимаем, что получаем ссылку потокобезопасно на массив (и его элементы) и могли бы творить всё, что угодно, но контракт приписывает использовать его только для чтения.
Сторонники Escape Анализа могут поспорить, но какие ваши доказательства ?.
Тем же,
ConcurrentLongObjectHashMap
Вполне очевидно, что одного Trove Collections для жизни мало, особенно, когда идёт речь о конкурентной среде.
Писать свой целый thread safe вариант для всех типов и видов коллекций не было
ни цели, ни желания, ни тем более времени.
В итоге на базе java.util.concurrent.ConcurrentHashMap появился ассоциативный список с long'ами в качестве ключей.
Оставим это упражнение для самостоятельного изучения читателю.
blobs
Существует не малый ряд задач, когда надо хранить большие объёмы (GBs) информации , которые надо просто отправлять в поток (stream) т.е. по-сути блобы. Соображение простое - объёмы данных таковы, что можно целиком (или почти целиком) загрузить в оперативную память, тем более, что она всё более и более доступна.
Типичные примеры - фото в социальных сетях, реплики данных в кластерах (hazelcast, terracota) и т.п.
Очевидно, что хранить каждое фото в виде отдельного объекта, пусть даже массива байт, накладно, когда таких объектов становится несколько миллионов прежде всего потому, как обход такого графа gc может занять существенное время, что приводит к отказу сервиса на некоторое время.
Тот самый случай, когда большой размер кучи враждует с garbage collector'ом.
Blob хранилище, как правило, располагают в off-heap - т.е один большой кусок памяти, за спиной у gc, над которым производят ручное управление памятью (custom memory mngmt).
Anything else ?
4 комментария:
"Но прежде всего это создание нового узла для сборщика мусора - даже если экземпляр обёртки будет жить быстро и умрёт молодым, сборщику придётся немного потрудится"
Можешь пояснить? Вроде, для copying collector никаких дополнительных расходов
@Denis Orlov
обход поколения за дополнительные расходы не считается ? зачем выполнять работу, которую можно не делать.
Так, насколько я понимаю, если узел к моменту обхода copy collectorа будет уже мертвый, то в обходе он участвовать и не будет.
Или ты имеешь в виду, что если меньще мусорить, то сборки (обходы поколения) будут случаться реже?
ps Как здесь подписаться на ответы к комментариям?
@Denis Orlov
если узел мёртв - он будет пропущен, но обход его всё равно будет, хотя и быстрый
и соот-но меньше молодого мусора -> реже minor сборки
Отправить комментарий