Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

5 «хаков» для уменьшения убыточных затрат при сборке мусора

Anna | 5.06.2014 | нет комментариев

В этом посте будут рассмотрены пять путей возрастания результативности кода, помогающие сборщику мусора проводить поменьше времени за выделением и освобождением памяти. Длинная процедура сборки мусора может привести к явлению, вестимому как «Stop the world».

Всеобщие данные

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

GC в этом случае использует так называемое «молодое поколение» («young generation») — секция кучи, где размещаются новые объекты. Всякий объект имеет поле «возраст» («age», находится в заголовке объекта), тот, что определяет сколько сборок мусора он испытал. Как только достигнут определенный возраст, объект копируется в иную область кучи, называемую «ветхим» («old») поколением.

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

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

1. Избегайте неявного применения строк

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

Не стоит забывать, что строки иммутабельны. Они не модифицируются позже аллокации. Операторы, такие как ” ” при объединении строк в реальности создают новейший объект String, содержащий конкатенацию строк. Ко каждому прочему, это приводит к неявному созданию объекта StringBuilder, тот, что и проводит саму операцию объединения.

Приведем пример:

a = a   b; // a и b - строки

А вот востребованный код, генерируемый компилятором за кулисами:

StringBuilder temp = new StringBuilder(a).
temp.append(b);
a = temp.toString(); // тут создается новая строка.
                     //  предыдущая “a” сейчас стала мусором.

В действительности все еще дрянней.
Разглядим дальнейший пример:

String result = foo()   arg;
result  = boo();
System.out.println(“result = “   result);

Тут мы имеем 3 StringBuilder’а выделенных неявно — по одному на всякую операцию ” ” и две дополнительных строки — одна, как итог второго присвоения, иная передается в способ println. В результате получили 5 дополнительных объектов в банальном коде.

Подумайте, что происходит в реальных программах, как бы генерации веб-страницы, работы с XML либо чтения текста из файла. Сходственный код внутри цикла приведет к возникновению сотен либо тысяч неявно выделяемых объектов. VM имеет механизмы борьбы с этим, но все имеет цену — и уплатят ее ваши пользователи.

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

StringBuilder value = new StringBuilder(“result = “);
value.append(foo()).append(arg).append(boo());
System.out.println(value);

Держа в уме, что в сходственных случаях строки и StringBuilder’ы выдаются неявно, вы сумеете значительно сократить число мелких аллокаций памяти в Зачастую выполняющемся коде.

2. Задавайте исходную вместимость списков

Динамически расширяемые коллекции, такие как ArrayList одни из основных конструкций, предуготовленных для оглавления данных переменной длины. ArrayList и другие коллекции, скажем, HashMap, TreeMap реализованы с применением нижележащего массива Object[]. Так же как строки (которые являются надстройками над массивами символов), размер массива постоянен. ?вственный вопрос — как получается добавлять элементы в коллекцию, при иммутабельном размере нижележащего массива? Результат не менее явствен — выделением больших массивов.

Разглядим дальнейший пример:

List<Item> items = new ArrayList<Item>();

for (int i = 0; i < len; i  ) {
  Item item = readNextItem();
  items.add(item);
}

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

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

Решение: указать исходную емкость всюду, где это допустимо:

List<MyObject> items = new ArrayList<MyObject>(len);

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

3. Используйте результативные коллекции простых типов

Нынешние версии компиляторов Java поддерживают обыкновенные и ассоциативные массивы с ключами и значениями простых типов посредством применения «автобоксинга» — оборачивания примитивного значения стандартным объектом, тот, что может быть выделен и удален GC.

Это может иметь некоторые отрицательные итоги. В Java огромная часть коллекций реализуется с применением внутренних массивов. Всякая пара ключ-значение добавленная в HashMap вызывает выделение внутреннего объекта для хранения обоих значений. Это неотвратимое зло помогает применению ассоциативных массивов — всякий раз когда в map добавляется элемент, это приводит к аллокации нового объекта и, допустимо, сборке ветхого. Существует и расходы, связанные с превышением вместимости, т.е. перевыделение источников под новейший внутренний массив. Когда мы имеем дело с огромным ассоциативным массивом, с тысячами а то и огромнее объектов, эти внутренние аллокации могут значительно повлиять на GC.

Частый случай — сберечь какое-либо отображение между простыми типами (скажем, идентификатор) и объектом. Так как HashMap предуготовлена для хранения объектных типов, это значит, что всякая вставка подразумевает создание еще одного объекта для «упаковки» значения примитивного типа.

Типовой способ Integer.valueOf() кэширует значения между -128 и 127, но всякое число вне данного диапазона приведет к выделению отдельного объекта для всякой пары ключ-значение. Это приводит ктройному GC overhead в всяком ассоциативном массиве. Для тех, кто прибыл из С , это может стать новостью — вследствие образцам в STL эта задача решена достаточно результативно.

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

4. Используйте Stream’ы взамен буферов в памяти

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

Обыкновенно чтение данных в память производится с применением ByteArrayInputStream, ByteBuffe, после этого итог передается на десериализацию.

Это может быть плохим подходом, т.к. вы обязаны сперва выделить, а после этого освободить место под данные, но лишь по окончании построения объектов из них. Но как правило размер данных не знаменит, что приведет, как вы теснее додумались, к непрерывному перевыделению памяти под массивы Byte[], которые будут расти при превышении вместимости буфера.

Решение крайне легко. Многие библиотеки, такие как нативный сериализатор Java, Protocol Buffers и т.д. способны строить десериализованные объекты, применяя данные напрямую из сетевого потока, т.е. не требуют хранения данных в памяти и внутренних массивах. По вероятности используйте данный подход — GC скажет спасибо.

5. Иммутабельность не неизменно благо

Иммутабельность — это великолепная вещь, но в случае высокопроизводительных вычислений может стать серьезным недостатком. Разглядим сценарий передачи между способами списочного объекта.

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

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

Пример 1. (Неэффективный)

List<Item> items = new ArrayList<Item>();

for (FileData fileData : fileDatas) {
  // Всякий вызов создает новейший промежуточный список
  // и, допустимо, какие-то внутренние массивы
  items.addAll(readFileItem(fileData));
}

Пример 2.

List<Item> items =  new ArrayList<Item>(
              fileDatas.size() * avgFileDataSize * 1.5);

for (FileData fileData : fileDatas) {
  readFileItem(fileData, items); // заполняем элементами внутри
}

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

Что еще почитать?

1) Про интернирование строк

2) Про результативные врапперы

3) Про Trove

4) Про Trove на Прогре

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB