Showing posts with label performance. Show all posts
Showing posts with label performance. Show all posts

Thursday, May 7, 2015

Сказ о том, как перформанс памятью убить

История начинается так… после очередного мержа и запуска проекта, обнаружилось, что производительность приложения упала почти до нуля. Загрузка CPU показывала почти 100%, что в свою очередь приводило к неимоверно долгой обработки запросов. После беглого анализа выяснилось, что тормозит собственно JVM. Как водится в таких случаях, начинаешь листать историю комитов с большей любознательность. В моем случае не было ничего подозрительного, что могло бы приводить к подобной картине. Немного подумав… запустил jvisualvm. Увидел приблизительно следующее:


Первая мысль… есть код, создающий большое количество мелких объектов, которые почти сразу же утилизируются сборщиком мусора. Или тоже самое, но только не в цикле, а во множестве тредов. Еще раз взглянул на историю комитов - ни тени компромата. Немного поразмыслив, обратил внимание на процентное отношение используемой памяти heap-а к доступной. Об этом и поговрим…

Незатейливый код

import java.util.ArrayList;
import java.util.Collection;
public class Main {
    public static void main(String[] args) throws Exception {
        final Collection data = new ArrayList();
        final Runtime runtime = Runtime.getRuntime();
        final int memoryThreshold = Integer.valueOf(args[0]);
        while (true) {

            int memoryUsed = (int) (100 * (runtime.totalMemory() - runtime.freeMemory()) / runtime.totalMemory());
            if (memoryUsed >= memoryThreshold) {
                Thread.sleep(1000);
                continue;
            }

            data.add(new Object());
        }
    }
}

Поясню… Создадим коллекцию и наполним ее объектами, которые не могут быть утилизированы сборщиком мусора. В момент, когда размер используемого heap-а достигнет порогового значения - работаем в холостую, периодически ставя текущий трэд на паузу. В качестве начального порога возьмем 15% от доступной памяти:

java -Xmx5m Main 15


Все чинно, благородно… Следующим шагом увеличим пороговое значение до 80%:


Можно заметить, что значение загрузки CPU подскочило до 70% и GC молотит без остановки. Объяснение я нашел такое… В момент, когда значения израсходованной памяти достигло определенного процента по отношению к доступной, JVM запускает сборщик мусора и, похоже, делает это с высокой степенью упорства и до бескоечности. Проверял на Java 6 и 7 с настройками GC по умолчанию - результат одинаковый.

О том что можно сделать

  • В некоторых случаях проблему можно решить увеличением значения Xmx
  • Почти уверен, что можно поиграться с опциями GC и получить приемлемые результаты
  • Если ни первое, ни второе не помогает, то стало быть остается одно - редизайн той части приложения, которая ест память

Ресурсы

  • jvisualvm - сейчас входит в состав JDK
  • mat - он же Memory Analyzer