В этой
статье, я постараюсь рассказать вам о JVM, и так
приступим что же за зверь такой JVM. Он состоит из трех основных частей: Java HotSpot Virtual Machine Runtime, JIT compiler, Garbage
Collector. JVMR – это
основная рабочая лошадка которая отвечает за многие вещи: когда вы запускаете,
JVM именно он обрабатывает параметры командной строчки, именно он подгружает
классы стандартным загрузчиком классов (class loader), он
отвечает за life cycle
виртуальной
машины, работает с jit
компилятором,
отвечает за обработку ошибок, обрабатывает потоки (thread) и отвечает за их синхронизацию, и ещё выполняет не заметно для
нас много полезных вещей. Для начала нам
нужно знать о параметрах командной строки. Давайте вспомним, какие параметры
могут иметь место в командной строчке виртуальной машины: стандартные опции
которые будут присутствовать во всех версиях вириальной машины, формат
параметра –Опция, например вывести
версию вириальной машины java
-version. Не стандартные которые могут не
поддерживаться в дальнейшем при реализации в следующих поколениях виртуальных
машин, начинаются такие параметры с –XОпция, например java
-XshowSettings:all . Продвинутые опции которыми нужно
пользоваться с осторожностью и если понимаете что делаете, опять могут не
поддерживаться во всех реализациях системы, параметры начинаются –XX,
делятся на два подтипа можете задать логический параметр, формат
такой –XX:[-,+]Опция, где “минус” выключить опцию, “плюс” включить опцию, например java -XX:+TraceClassLoading
включить трассировку загрузки классов в виртуальной машине. Второй
подтип вы задаете конкретно значение параметра –XX:Параметр=значениеПараметра,
например, установить параметр MaxHeapSize
в 256 Мегабайт: java -XX:MaxHeapSize=256m. Ну, вот и все мы
рассмотрели кратко, как задавать параметры виртуальной машины. А теперь, напишем
простейшую программу, и на примере рассмотрим, что происходит с ней и как все
работает в виртуальной машине:
package org.vit.ch1;
public class
Test {
public Test() {
System.out.printf("%s","Hello JVM!");
}
public static void main(String args[]) {
new
Test();
}
}
И так
первым делом мы должны преобразовать наш
java код в байт код, т. е. преобразовать
файл Test.java в
Test.class, делается это все просто: в командной строке набираем javac Test.java получаем Test.class,
переходим в каталог, где у вас
начинается пакет и запускаем оттуда команду на запуск виртуальной машины:
C:\jboss-4.2.0.GA\vit\poi_excel\c1\target\classes>java
-cp "." org.vit.ch1.Test
Hello JVM!
Но за счет кроссплатформенности и переносимости мы теряем в производительности т. к. любой интерпретированный код всегда будет работать медленней машинного кода, это факт. И так не спешите расстраиваться, любая программа скомпилированная под одну конкретную архитектуру и версию операционной системы будет, себя хуже вести в более новой или старой версии ОС, а если поменялась и разрядность “аппаратной архитектуры” с x32 на x64, то падение производительности программы трудно не заметить. И так что же делать, нужно, если есть возможность достать исходники и перекомпилировать программу в родной машинный код, это возможно только в Linux подобных системах с открытым исходными кодами. Для примера допустим вы установили ядро linux, и у вас много не нужных модулей этого ядра которые “мешаются”, вы просто пере собираете ядро, отключив при этом ненужные модули и компилите его под вашу “аппаратную архитектуру”, при этом у вас получается более шустрое ядро с родными машинными инструкциями (машинным кодом). Примерно так же поступает JVM HotSpot, преобразовывает байт код в машинный и не просто преобразовывает, а пытается анализировать одни и те же куски кода, которые часто выполняются и оптимально преобразовывать их в машинные инструкции, так же он собирает статистику выполнения и на основе этой статистику опять перестраивает код, перекомпилирует его уже с учетом этой информации. На практике встречаются случаи, когда оптимизированная программа на виртуальной машине становится более производительной с течением времени чем, подобная программа в машинных кодах. Все эти манипуляции JVM не проходят даром, и во-первых нужно запустить саму виртуальную машину, во-вторых на jit компиляцию затрачиваются ресурсы системы. По этой причине вам поставляются две виртуальные машины, с двумя различными jit компиляторами которые вы можете запустить, выбрав параметры при запуске JVM: -client или –server. И так вернемся немного назад, с начало были jvm которые только интерпретировали код, их признали медленными и отказались от них. После этого появился jit компилятор который преобразовывал байт код в машинный, при этом он делал это не спеша, компилировал только те куски кода, которые будут выполняться сейчас, остальные не трогал от это он получил название Just-It-Time (jit). Этот принцип работы дает возможность программе стартовать раньше, нежели, если бы весь код был бы откомпилирован виртуальной машиной. И так мы получили замедление при старте программы, но решили проблему медлительности интерпретаторов предыдущего поколения виртуальных машин. И так JVM HotSpot вобрал в себя функции интерпретатора – часть кода по-прежнему интерпретируется, не часто используемые куски байт кода; компилятора – части кода, которые выполняются наиболее часто, преобразовываются в скомпилированный машинный код, и оптимизатора профилировщика который отслеживает и непрерывно старается оптимизировать “горячие куски” кода (от англ. “Hot Spots” – и пошло слово HotSpot). И так подведем итог: вместо преобразования всех байт кодов в машинный JVM начинает работу как интерпретатор и компилирует только "горячие куски" кода, то есть код, выполняющийся наиболее часто. Во время выполнения он собирает данные анализа. Эти данные используются для определения фрагментов кода, выполняющихся достаточно часто и заслуживающих компиляции. Компиляция только часто исполняемого кода имеет несколько преимуществ: не тратится время на компиляцию кода, выполняющегося редко, таким образом, компилятор может тратить больше времени на оптимизацию "горячего" кода, поскольку он знает, что время будет потрачено не зря. Кроме того, откладывая компиляцию, компилятор имеет доступ к данным анализа, которые могут быть использованы для улучшения оптимизации.
Виртуальная машина, запушенная с параметром –server имеет компилятор, оптимизированный для повышения скорости работы и предназначен для протяженных по времени серверных приложений, плюс не стесняется потреблять ресурсы системы. Виртуальная машина, запушенная с параметром –client имеет компилятор, который оптимизирован для уменьшения времени начального запуска приложения и занимаемого объема памяти, реализует менее сложную оптимизацию, чем серверный компилятор, и, следовательно, требует меньше времени для компиляции, используется в основном для клиентских программ.
Надо сказать
ещё об одном параметре виртуальной машины -XX:+TieredCompilation,
это
гибридный параметр, который вобрал в себя все лучшее из двух различных
виртуальных машин, быстроту запуска, от клиентской и оптимизацию от
серверной. Этот параметр запускается
парой с параметром –server -XX:+TieredCompilation,
он
по умолчанию включен у 64-битных серверных виртуальных машин, подходит для запуска,
как клиентских приложений,
так и серверных. И так попробуем
запустить виртуальные машины с параметром –version которая показывает версию
виртуальной машины:
Microsoft Windows XP [Версия
5.1.2600]
(С) Корпорация Майкрософт,
1985-2001.
C:\Documents
and Settings\lopanov>java -version
java
version "1.7.0_17"
Java(TM)
SE Runtime Environment (build 1.7.0_17-b02)
Java HotSpot(TM) Client VM
(build 23.7-b01, mixed
mode, sharing)
C:\Documents
and Settings\lopanov>
Microsoft
Windows [Version 6.1.7601]
(c)
Корпорация Майкрософт (Microsoft Corp.), 2009. Все права защищены.
C:\Users\kamaz1>java
-version
java
version "1.8.0_60"
Java(TM)
SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM
(build 25.60-b23, mixed
mode)
C:\Users\kamaz1>
Мы
запустили две виртуальные машины на разных компьютерах, с разными операционными
системами проанализируем полученную
информацию. По умолчанию, в зависимости
от операционной системы и её разрядности, виртуальные машины настроены на
определенный тип: на серверный или на клиентский, и у них есть настройки по
умолчанию, все основные дефолтные параметры
виртуальной машины, можно посмотреть,
включив опцию -XX:+PrintCommandLineFlags
для примера на win7:
C:\jboss-4.2.0.GA\vit\poi_excel\c1\target\classes>java
-cp "." -XX:+PrintCommandLineFlags
org.vit.ch1.Test
-XX:InitialHeapSize=133309504 -XX:MaxHeapSize=2132952064
-XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers
-XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation
-XX:+UseParallelGC
Hello JVM!
Т.е.
запуская просто java -cp "."
org.vit.ch1.Test вы по умолчанию запускаете виртуальную
машину с дефолтными параметрами. Все это
называется енергономикой системы, которые
можно посмотреть и кратко почитать на английском.
И так вернемся к нашим примерам. Для win XP – я смог только запустить клиентскую VM, для win7 только серверный вариант VM, вот такие ограничения
есть у windows, у
других ОС я слышал более широкие возможности … . И так что же такое mixed mode
- эта опция jit компилятора, про которую я вам рассказывал, она установлена по
умолчанию -Xmixed, выполнять весь байт код в режиме интерпретатора, кроме “горячих
кусков кода” которые компилируются в машинный код. Есть режим –Xint запускается виртуальная машина только в режиме интерпретатора, нет
компиляции в нативный (родной) машинный код методов, все интерпретируется. Есть режим –Xcomp который,
заставляет jit
компилятор
компилировать байт код при первом вызове метода. По умолчанию, байт код компилируется в
машинный код после интерпретации его определенное количество раз. Этот
параметр равен 10 000 для серверной VM и 1 000
для клиентской VM, его
можно выставить параметром -XX:CompileThreshold=5 000, метод
откомпилируется после прогона его интерпретатором на пяти тысячный раз. Но
на этом оптимизация не заканчивается, JVM продолжает анализ и может
перекомпилировать код заново с более высоким уровнем оптимизации, если решит,
что метод является особенно "горячим" или последующий анализ данных
показал возможность дополнительной оптимизации. И так, какой можно сделать вывод из этого, что
бы выполнять тесты производительности и замеры вам нужно предварительно “прогреть
программу”, её “горячие куски” и только после этого снимать замеры
производительности. Давайте познакомимся с еще одним интересным параметром -XX:+PrintFlagsFinal он показывает все опции, которые включены у
виртуальной машины, найдем значение CompileThreshold
по
умолчанию:
java -cp "."
-XX:+PrintFlagsFinal org.vit.ch1.Test
….
intx CompileThreshold = 10000 {pd product}
….
У меня вышло более 700 параметров, которые вы в основном,
можете менять у виртуальной машины, но обычно работают с парой десятков центровых параметров, после
производят замеры производительности, анализ и разбор полетов и так по кругу
пока не устроит результат оптимизации. У
winXP есть параметр sharing - в java 5
появилась такая вещь как Class Data Sharing. Это файл на диске, в моем случае C:\Program
Files\Java\jre1.8.0_60\bin\server\classes.jsa, являющийся частью инсталляции
JVM, который содержит дапм JVM памяти, содержащий загруженные классы образующие
ядро JVM, т.е. те которые наверняка JVM пришлось бы загрузить сразу при старте.
Имея такую область, она сразу мапится при инициализации java машины, тем самым
ускоряя время старта приложения. Если файл classes.jsa существует, его всегда
можно удалить а после перегенирировать командой -Xshare:dump :
C:\Users\kamaz1>java -Xshare:dump
Allocated shared space: 37879808 bytes at 0x0000000800000000
Loading classes to share ...
Loading classes to share: done.
Rewriting and linking classes ...
Rewriting and linking classes: done
Number of classes 2482
instance classes =
2468
obj array classes = 6
type array classes = 8
Calculating fingerprints ... done.
Removing unshareable information ... done.
Shared Lookup Cache Table Buckets = 8216 bytes
Shared Lookup Cache Table Body = 64792 bytes
ro space: 6505640 [ 35.3% of
total] out of 16777216 bytes [38.8%
used] at 0x0
000000800000000
rw space: 10391224 [ 56.3% of
total] out of 16777216 bytes [61.9%
used] at 0x0
000000801000000
md space: 1524696 [ 8.3% of total] out of 4194304 bytes [36.4% used] at 0x0
000000802000000
mc space: 34053 [ 0.2% of total] out of 131072 bytes [26.0% used] at 0x0
000000802400000
total : 18455613 [100.0% of total] out of 37879808 bytes [48.7% used]
Рассмотрим параметр -Xshare:mode: on
- включает CDS, off –
отключает, по умолчанию эта опция выбрана у виртуальных машин Java HotSpot 32-Bit Server VM, Java HotSpot 64-Bit Client VM, and Java
HotSpot 64-Bit Server VM, auto – включается режим CDS если нужно, по
умолчанию включено у
Java HotSpot 32-Bit
Client VM. CDS советуют включать, на клиентских машинах с ограниченной памятью, где
запускается много виртуальных машин, и одна область памяти CDS будет использоваться для
различных процессов совместно, несколькими jvm. Включим параметр -Xshare:on и посмотрим, какие же флаги добавились:
C:\jboss-4.2.0.GA\vit\poi_excel\c1\target\classes>java
-cp "." -Xshare:on
-XX:+PrintCommandLineFlags org.vit.ch1.Test
-XX:InitialHeapSize=133309504
-XX:MaxHeapSize=2132952064 -XX:+PrintCommandLineFlags -XX:+RequireSharedSpaces
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC -XX:+UseSharedSpaces
Hello JVM!