Как известно, приложение не всегда использует одинаковое количество ресурсов, но благодаря функции автоматического вертикального масштабирования, в Jelastic изменяется размер контейнера под приложение. Соответственно пользователю не нужно переплачивать за зарезервированные ресурсы, которые не используются, как в случае с другими PaaS. Ведь действительно, бизнес-модель всей хостинговой индустрии и старого поколения PaaS решений основана на «overselling». Таким образом, важность справедливой оплаты за фактическое потребление ресурсов очевидна.
Понятное дело, что в то время, когда разрабатывали JVM, никто не знал об облаках или виртуализации, и, тем более, никто даже не задумывался о плотности в PaaS. Сегодня виртуализация изменила ход игры в хостинговой индустрии, и эта революция будет продолжаться. Теперь мы можем более эффективно использовать ресурсы. Майкл Видстендт, один из главных архитекторов JVM в Oracle, подтвердил, что JVM совсем не предназначена для PaaS, но Oracle делает все возможное, чтобы изменить это. Плюс ребята из IBM двигаются в том же направлении. Некоторые примечания о динамическом поведением JVM можно найти в IBM JavaOne Keynote 2012 Highlights.
Честно говоря, «изобрести» вертикальное масштабирование было нелегко в силу ряда технических ограничений. Одно из таких ограничений связано непосредственно с JVM. В этой статье мы хотели бы поделиться некоторой информацией, которая может быть полезна для понимания вертикального масштабирования и вышеупомянутых ограничений.
Для начала мы создали приложение, которое само контролировало потребление ресурсов и через API, когда оно доходило до верхней границы, давало команду инфраструктуре увеличить лимит, и наоборот, когда ресурсы не нужны были, давало команду забрать их обратно. Такой алгоритм работал достаточно неплохо, но в процессе тестирования выяснился еще один нюанс: Java не очень хорошо работает с масштабированием, так как она может стартануть с маленьким объемом памяти, потом взять дополнительные ресурсы, когда это необходимо, но обратно в OC она их не возвращает. Это специально было продумано инженерами JVM, чтобы ускорить процесс получения памяти, когда она понадобится в следующий раз. Разумеется, что для нашей облачной модели это не приемлемо.
Затем мы провели еще серию экспериментов, как ведет себя Java, точнее garbage collectors, такие как: Serial, Parallel, Concurrent Mark Sweep и G1.
Как работает Garbage Collection
В Java, динамического выделения объектов достигается с помощью нового оператора. Объект, как только создается, использует часть памяти, а память остается зарезервированной до тех пор, пока нет обращения к объекту. В случае, когда обращение к объекту не происходит, предполагается, что он больше не нужен, и память, занимаемая им, может быть освобождена. Нет явной необходимости уничтожать сам объект, так как Java может автоматически переместить его в памяти. Сбор мусора как раз является тем методом, который решает данную задачу. Программы, вовремя не освобождающие ненужные уже участки памяти, могут вызвать неконтролируемое уменьшение объёма свободной памяти — так называемую «утечку памяти». В Java, сбор мусора происходит автоматически на протяжении всего жизненного цикла программы, устраняя необходимость в освобождении памяти и, таким образом, предотвращает утечки. Более подробную информацию о Garbage Collection в Java можно найти в книге Java Enterprise Performance Book или на блоге Javarevisted.
Serial Garbage Collector (-XX:+UseSerialGC)
Начнем с Serial. Он использует один поток для выполнения всех работ по сборке мусора. Лучше всего подходит для однопроцессорных машин, иногда может быть полезен и для многопроцессорных, на которых запускаются приложения с небольшими наборами данных (примерно до 100 МБ).
Этот garbage collector показал очень хороший результат относительно масштабирования. Мы выяснили, что он обладает свойствами компактинации, то есть можно сказать, что он делает дефрагментацию памяти, и неиспользуемые ресурсы возвращает в OС.
Давайте еще раз в этом убедимся, протестировав Serial Garbage Collector на JVM 6 и JVM 7.
Java 6
Если точнее, будем тестировать Java версии 1.6.0_27.
Запускаем утилиту Visual JavaVM, которая дает возможность мониторить все Java-поцессы, запущенные на компьютере, а также отслеживать, сколько эти процессы потребляют памяти.
Для тестирования будем использовать программу, которая динамически выделает и освобождает память в бесконечном цикле:
public class Memoryleak {
public static void main(String[] args) {
System.out.println("START....");
while (true) {
System.out.println("next loop...");
try {
int count = 1000 * 1024;
byte [] array = new byte[1024 * count];
Thread.sleep(5000);
array = null;
System.gc();
System.gc();
Thread.sleep(5000);
} catch (InterruptedException ex) {
}
}
}
}
Запускаем JVM с такими параметрами:
-XX:+UseSerialGC -Xmx1024m -Xmn64m -Xms128m -Xminf0.1 -Xmaxf0.3, где
-XX:+UseSerialGC — использовать Garbage Collector G1;
-Xmx1024m — максимальное использование ОЗУ – 1024 Мб;
-Xmn64m — heap-шаг равный 64 Мб;
-Xms128m – минимальное значение heap равное 128 Мб;
-Xminf0.1 — этот параметр определяет минимальное свободное пространство в heap и поручает JVM расширить его, если после выполнения сборки мусора нет, по крайней мере, 10% свободного пространства;
-Xmaxf0.3 — этот параметр определяет насколько heap расширен и поручает JVM уплотнить его, если объем свободного пространства превышает 30%.
По умолчанию значения для Xminf и Xmaxf равны 0.3 и 0.6, соответственно, но для эксперимента мы существенно уменьшили эти пределы, чтобы увеличить амплитуду вертикального масштабирования.
В результате, по отдельно взятому процессу выполнения нашей программы, видим, что память динамически заполняется и освобождается:
Давайте так же взглянем на график суммарно потребляемой памяти, чтобы убедиться, что нет никаких утечек:
Java 7
Будем тестировать версию jdk 1.7.0_03
Используем ту же программу и устанавливаем те же параметры для JVM. Как видим с JDK 7 все тоже гладко:
Хороший результат, но, к сожалению, как мы уже упоминали выше, данный garbage collector работает только с однопроцессорными машинами и небольшими объемами памяти, чего недостаточно для функционирования больших, громоздких приложений.
Parallel Garbage Collector (-XX:+UseParallelGC)
Parallel может выполнять мелкие работы по сборке мусора параллельно, что позволяет значительно снизить расход ресурсов и времени. Это полезно для приложений со средним и большим набором данных, которые работают на многопроцессорных и многопоточных машинах.
Давайте повторим наш эксперимент для Parallel Garbage Collector.
Результат по суммарно потребляемой памяти для Java 6:
И для Java 7:
Parallel, конечно, обладает многими преимуществом по сравнению с Serial, например: может работать с мультипоточными приложениями, мультипроцессорными машинами, быстро справляется с большими объемами памяти. Но, увы, как мы видим на графиках, он не возвращает неиспользуемые ресурсы обратно в операционную систему.
Concurrent Mark Sweep Garbage Collector (-XX:+UseConcMarkSweepGC)
Concurrent Mark Sweep выполняет большую часть работ, пока приложение все еще запущено. Это позволяет существенно уменьшить паузы на сборку мусора. Он предназначен для приложений со средним и большим набором данных, для которых время отклика важнее, чем общая пропускная способность.
Повторим процедуру еще раз для Concurrent Mark Sweep.
И опять ситуация аналогична предыдущей:
Java 6:
Java 7:
G1 Garbage Collector (-XX:+UseG1GC)
И тут появляется G1, который обладает всеми преимуществами Parallel и Concurrent Mark Sweep, и удовлетворяет всем нашим требованиям.
Мы приступили к его тестированию, и выяснилось, что при продолжительной работе G1 наблюдается стабильная константа утечки памяти.
Java 6:
Конечно же, мы обратились с этой проблемой к представителям Oracle. Сотрудники Oracle Иван Крылов и Владимир Иванов из Санкт-Петербурга оперативно отреагировали на наш запрос и в результате данная проблема устранена в JVM 7. Давайте проверим:
Как видим, действительно в Java 7 все ok, усовершенствованный G1 выполнил свою миссию полностью.
Как Вы понимаете, возможность платить по факту использования ресурсов, является очень важным фактором для каждого клиента и компании. Никто не должен переплачивать. Однако, есть еще несколько блокеров, которые предотвращают быстрое развитие в этом направлении. Мы и дальше будем делать все возможное, чтобы продолжить революцию в хостинговой индустрии.
Видео ниже объясняет, как автоматическое вертикальное масштабирование работает в Jelastic:
*По неизвестным причинам официальный баг по нашему отчету об утечке памяти в G1 был удален из базы данных Oracle. Более подробную информацию об этом баге можно найти здесь.
Автор: jelastic