История начинается так… после очередного мержа и запуска проекта, обнаружилось, что производительность приложения упала почти до нуля. Загрузка 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и получить приемлемые результаты - Если ни первое, ни второе не помогает, то стало быть остается одно - редизайн той части приложения, которая ест память

