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

5 comments:

  1. Попробуй вывести значение переменной memoryUsed в блоке оператора if и запустить программу с флагом -XX:+PrintGC. Можно будет увидеть, что GC перестает работать, после того, как мы достигаем границы используемой памяти и перестаем создавать объекты.

    ReplyDelete
  2. The actual time and effort took to create this wonderful article were really great and would like to read this blog regularly to get more updates...
    Java Training in Chennai | AWS Online Training

    ReplyDelete
  3. We at Mytanfarma help you to realize three ultimate goals :
    PROFIT to customer - Managed farmland in Bangalore offers the potential for higher rates of return due to the scope for appreciation in the value of land, tax-free agricultural income, and value enhancement of the land through forestry and plantation crops. Horticultural crops can provide seasonal income, while timber can fetch handsome returns as a long-term benefit.

    ReplyDelete