Как известно, приложение не всегда использует одинаковое количество ресурсов, но благодаря функции автоматического вертикального масштабирования, в 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.
В Java, динамического выделения объектов достигается с помощью нового оператора. Объект, как только создается, использует часть памяти, а память остается зарезервированной до тех пор, пока нет обращения к объекту. В случае, когда обращение к объекту не происходит, предполагается, что он больше не нужен, и память, занимаемая им, может быть освобождена. Нет явной необходимости уничтожать сам объект, так как Java может автоматически переместить его в памяти. Сбор мусора как раз является тем методом, который решает данную задачу. Программы, вовремя не освобождающие ненужные уже участки памяти, могут вызвать неконтролируемое уменьшение объёма свободной памяти — так называемую «утечку памяти». В Java, сбор мусора происходит автоматически на протяжении всего жизненного цикла программы, устраняя необходимость в освобождении памяти и, таким образом, предотвращает утечки. Более подробную информацию о Garbage Collection в Java можно найти в книге Java Enterprise Performance Book или на блоге Javarevisted.
Начнем с Serial. Он использует один поток для выполнения всех работ по сборке мусора. Лучше всего подходит для однопроцессорных машин, иногда может быть полезен и для многопроцессорных, на которых запускаются приложения с небольшими наборами данных (примерно до 100 МБ).
��тот garbage collector показал очень хороший результат относительно масштабирования. Мы выяснили, что он обладает свойствами компактинации, то есть можно сказать, что он делает дефрагментацию памяти, и неиспользуемые ресурсы возвращает в OС.
Давайте еще раз в этом убедимся, протестировав Serial Garbage Collector на JVM 6 и JVM 7.
Java 6
Если точнее, будем тестировать Java версии 1.6.0_27.
Запускаем утилиту Visual JavaVM, которая дает возможность мониторить все Java-поцессы, запущенные на компьютере, а также отслеживать, сколько эти процессы потребляют памяти.
Для тестирования будем использовать программу, которая динамически выделает и освобождает память в бесконечном цикле:
Запускаем JVM с такими параметрами:
-XX:+UseSerialGC -Xmx1024m -Xmn64m -Xms128m -Xminf0.1 -Xmaxf0.3, где
-XX:+UseSerialGC — использовать Garbage Collector Serial;
-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 может выполнять мелкие работы по сборке мусора параллельно, что позволяет значительно снизить расход ресурсов и времени. Это полезно для приложений со средним и большим набором данных, которые работают на многопроцессорных и многопоточных машинах.
Давайте повторим наш эксперимент для Parallel Garbage Collector.
Результат по суммарно потребляемой памяти для Java 6:

И для Java 7:

Parallel, конечно, обладает многими преимуществом по сравнению с Serial, например: может работать с мультипоточными приложениями, мультипроцессорными машинами, быстро справляется с большими объемами памяти. Но, увы, как мы видим на графиках, он не возвращает неиспользуемые ресурсы обратно в операционную систему.
Concurrent Mark Sweep выполняет большую часть работ, пока приложение все еще запущено. Это позволяет существенно уменьшить паузы на сборку мусора. Он предназначен для приложений со средним и большим набором данных, для которых время отклика важнее, чем общая пропускная способность.
Повторим процедуру еще раз для Concurrent Mark Sweep.
И опять ситуация аналогична предыдущей:
Java 6:
Java 7:

И тут появляется G1, который обладает всеми преимуществами Parallel и Concurrent Mark Sweep, и удовлетворяет всем нашим требованиям.
Мы приступили к его тестированию, и выяснилось, что при продолжительной работе G1 наблюдается стабильная константа утечки памяти.
Java 6:

Конечно же, мы обратились с этой проблемой к представителям Oracle. Сотрудники Oracle Иван Крылов и Владимир Иванов из Санкт-Петербурга оперативно отреагировали на наш запрос и в результате данная проблема устранена в JVM 7. Давайте проверим:

Как видим, действительно в Java 7 все ok, усовершенствованный G1 выполнил свою миссию полностью.
Как Вы понимаете, возможность платить по факту использования ресурсов, является очень важным фактором для каждого клиента и компании. Никто не должен переплачивать. Однако, есть еще несколько блокеров, которые предотвращают быстрое развитие в этом направлении. Мы и дальше будем делать все возможное, чтобы продолжить революцию в хостинговой индустрии.
Видео ниже объясняет, как автоматическое вертикальное масштабирование работает в Jelastic:
*По неизвестным причинам официальный баг по нашему отчету об утечке памяти в G1 был удален из базы данных Oracle. Более подробную информацию об этом баге можно найти здесь.
Понятное дело, что в то время, когда разрабатывали 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 Serial;
-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. Более подробную информацию об этом баге можно найти здесь.