Расширения инфраструктуры совместно используемых классов, впервые появившейся в 5-й версии JRE от IBM для платформы Java SE, улучшают производительность Java-приложений, сокращая время запуска и оптимизируя работу с памятью. В этой статье мы расскажем о сделанных изменениях и проиллюстрируем преимущества использования совместно используемых классов на примере Eclipse и Apache Tomcat в качестве клиентского и серверного приложения соответственно. Мы приведем инструкции для установки, чтобы вы могли попробовать самостоятельно поработать, однако вы должны быть знакомы с обоими приложениями, а также с совместно используемыми классами от IBM. Если вы не знакомы с возможностями совместно используемых классов от IBM, мы рекомендуем вам начать со статьи "Технологии Java в стиле IBM: Совместно используемые классы" (EN), раскрывающей базовые концепции этой функциональности.
Вы можете загрузить реализацию JRE Java 6 от IBM для Linux® и AIX® прямо сейчас, если вы хотите работать с примерами. . Хотя отдельного загрузочного пакета JRE Java 6 от IBM для Windows® не существует, эта реализация встроена в сборку Eclipse, которую можно загрузить по этой ссылке. Заметьте, что для загрузки необходимо иметь регистрацию (бесплатную) в системе IBM.
Что нового в совместно используемых классах от IBM?
В JRE IBM для Java 5 появилась возможность организовывать совместный доступ к классам для нескольких виртуальных машин, используя кэш. Теперь в JRE от IBM для Java 6 этот кэш можно сделать постоянным и использовать его для совместного доступа к компилируемому коду. Метод хранения данных в кэше также стал более эффективным.
Возможность для виртуальных Java-машин совместно использовать классы впервые появилась в JRE от IBM для Java 5 и продолжает поддерживаться и расширяться в Java 6. Классы, загруженные виртуальной Java-машиной, можно помещать в кэш. При последующих запросах класс будет по возможности извлекаться из кэша, а не соответствующего JAR-файла.
Максимальным размером этого кэша можно управлять, используя параметр командной строки, как показано в листинге 1, но помните, что этот максимальный размер может зависеть от ограничений операционной системы на совместно используемую память:
Листинг 1. Параметр командной строки, задающий максимальный размер кэша
Running java -X will show the following option ...
Arguments to the following options are expressed in bytes.
Values suffixed with "k" (kilo) or "m" (mega) will be factored accordingly.
:
-Xscmx<x> set size of new shared class cache to <x>
:
|
Хранение заранее скомпилированного (Ahead of Time или AOT) кода
Виртуальная Java-машина, как правило, компилирует методы в машинный (платформеннозависимый) код во время выполнения программы. Машинный код генерируется во время каждого запуска программы. В JRE от IBM для виртуальной машины Java 6 SR1 появилась возможность использования технологии компиляции перед исполнением (Ahead of Time или AOT) для создания машинного кода, который можно использовать не только в данной Java-машине, но и помещать в кэш совместно используемых классов. Другая Java-машина, запущенная с использованием кэша совместно используемых классов, заполненного AOT-кодом во время предыдущих запусков программ Java, может использовать AOT-код из кэша для сокращения времени старта. Это сокращение обеспечивается за счет экономии времени, необходимого для компиляции и за счет более быстрого выполнения методов в виде AOT-кода. Код AOT – это машинный код, который, как правило, выполняется быстрее, чем интерпретируемый код (хотя маловероятно, что он будет выполняться быстрее кода, сгенерированного в процессе JIT-компиляции).
Минимальный и максимальный размер части кэша совместно используемых классов, выделенной под код AOT, можно задать с помощью параметров командной строки, как показано в листинге 2. Если вы не укажете максимальный допустимый размер кода AOT, по умолчанию для него будет доступен весь объем кэша. Однако это не означает, что в таком случае весь кэш будет заполнен кодом AOT, так как код AOT может быть сгенерирован только из классов, уже находящихся в кэше.
Листинг 2. Параметры командной строки, управляющие объемом кэша, доступного для хранения кода AOT
Running java -X will show the following options ...
Arguments to the following options are expressed in bytes.
Values suffixed with "k" (kilo) or "m" (mega) will be factored accordingly.
:
-Xscminaot<x> set minimum shared classes cache space reserved for AOT data to <x>
-Xscmaxaot<x> set maximum shared classes cache space allowed for AOT data to <x>
|
На рисунке 1 показано, как пространство кэша заполняется совместно используемыми классами и кодом AOT, а также как с помощью параметров кэша можно управлять долей общего отводимого под них места.
Рисунок 1. Пример структуры кэша совместно используемых классов
Узнайте подробнее о коде AOT.
Для наиболее эффективной работы с кэшем совместно используемых классов виртуальная Java-машина использует технологию сжатия, увеличивающую количество классов, которые можно хранить в кэше. Сжатие классов выполняется автоматически и не может быть изменено с помощью параметров командной строки.
В JRE от IBM для Java 5 кэш совместно используемых классов был реализован с помощью сегментов совместно используемой памяти, что позволяло виртуальным Java-машинам совместно использовать один и тот же кэш, но этот кэш очищался при перезагрузке операционной системы. Это значило, что первая Java-машина, запущенная после перезагрузки, должна была создавать кэш заново. В Java 6 реализация кэша по умолчанию использует отображающийся в память файл, что позволяет сохранять кэш при перезагрузке операционной системы.
Компилятор AOT – это дополнительный механизм компиляции, появившийся в JRE IBM для Java 6. В предыдущих версиях JRE IBM метод мог быть выполнен либо интерпретацией всех байт-кодов Java, составляющего этот метод, либо как машинный код, скомпилированный и оптимизированный JIT (Just-in-Time) компилятором, входящим в JRE. JIT компилирует код динамически при фактическом вызове метода, при этом применяемые приемы компиляции определяются в процессе анализа метода во время выполнения.
Код AOT – это машинный код Java-метода, сгенерированный в процессе AOT-компиляции. В отличие от JIT-компиляции, AOT-компиляция не оптимизирует код на основе динамического анализа выполняемого Java-метода. Как правило, машинный код, полученный в результате AOT-компиляции, выполняется быстрее, чем интерпретируемый байт-код Java, однако не так быстро, как машинный код, сгенерированный во время JIT-компиляции.
Главной целью AOT-компиляции является ускорение запуска приложения за счет предварительной компиляции некоторых Java-методов. Чтобы получить готовый для выполнения машинный код Java-метода, гораздо быстрее загрузить предварительно скомпилированный AOT метод, чем генерировать его машинный код с помощью JIT-компиляции. Быстрая загрузка скомпилированного кода AOT позволяет виртуальной Java-машине тратить меньше времени на получение машинного кода исполняемого метода. Скомпилированные AOT методы в дальнейшем также могут подвергаться JIT-компиляции, т.е. после первоначального выполнения в виде кода AOT метод может быть оптимизирован JIT-компилятором.
AOT как часть совместно используемых классов
Сгенерированный код AOT хранится в области кэша совместного доступа. Любая другая виртуальная Java-машина, использующая этот совместно используемый класс, может выполнять этот метод как код AOT, не расходуя при этом ресурсы на компиляцию.
Эта реализация отличается от виртуальной Java-машины реального времени. Как описано в статье "Real-time Java, Part 1: Using the Java language for real-time systems" (EN), там компиляция кода AOT выполняется утилитой (jxeinajar), а сам код хранится в jar-файле.
Код AOT, выполняемый виртуальной Java-машиной, не используется совместно, а копируется из кэша совместно используемых классов. При таком подходе отсутствует непосредственный выигрыш в объеме используемой памяти, так как каждая Java-машина работает со своей копией исполняемого кода AOT, однако некоторая экономия ресурсов памяти и процессора все же достигается за счет возможности использования существующего кода вместо повторения компиляции.
Чтобы понять, какие методы в вашем приложении были подвергнуты AOT-компиляции и как много места эти методы занимают в кэше совместно используемых классов, можно использовать три следующих параметра командной строки:
-Xjit:verbose: команда позволяет получить информацию о каждой AOT-компиляции, выполняемой JIT-компилятором.-Xshareclasses:verboseAOT: : команда позволяет получить информацию о коде AOT, прочитанном или сохраненном в кэше совместно используемых классов.
java -Xshareclasses:printAllStats: команда служит для вывода статистики работы кэша совместно используемых классов, в том числе о хранимом коде AOT и о занимаемом месте.
В листинге 3 показана информация, выводимая при первом вызове
сервера Tomcat с параметрами
-Xjit:verbose и
-Xshareclasses:verboseAOT, выполненном после очистки кэша совместно используемых классов:
Листинг 3. Использования параметров
-Xjit:verbose и -Xshareclasses:verboseAOT+ (AOT cold) java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder; Storing AOT code for ROMMethod 0x02359850 in shared cache... Succeeded. + (AOT cold) sun/misc/URLClassPath$JarLoader.ensureOpen()V @ 0x0147BF9C-0x0147C106 Q_SZ=3 Storing AOT code for ROMMethod 0x023CBFC4 in shared cache... Succeeded. + (AOT cold) java/util/jar/JarFile.getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry; Storing AOT code for ROMMethod 0x023CE38C in shared cache... Succeeded. |
После инициализации сервера Tomcat статистика кэша совместно используемых классов, полученная командой
java
-Xshareclasses:printAllStats показывает, что эти методы были помещены в кэш (как видно из фрагмента, приведенного в листинге 4):
Листинг 4. Использование команды
java
-Xshareclasses:printAllStats для получения методов, хранимых в кэше совместно используемых классов
1: 0x43469B8C AOT: append
for ROMClass java/lang/StringBuilder at 0x42539178.
1: 0x43469634 AOT: ensureOpen
for ROMClass sun/misc/URLClassPath$JarLoader at 0x425AB758.
1: 0x434693A8 AOT: getEntry
for ROMClass java/util/jar/JarFile at 0x425ADAD8.
|
Как показано в листинге 5, при последующих запусках сервера Tomcat с использованием совместно используемых классов вместо повторной компиляции эти методы будут загружены из кэша в уже скомпилированном (AOT) виде:
Листинг 5. Нахождение и загрузка скомпилированных методов AOT
Finding AOT code for ROMMethod 0x02359850 in shared cache... Succeeded.
(AOT load) java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
Finding AOT code for ROMMethod 0x023CBFC4 in shared cache... Succeeded.
(AOT load) sun/misc/URLClassPath$JarLoader.ensureOpen()V
Finding AOT code for ROMMethod 0x023CE38C in shared cache... Succeeded.
(AOT load) java/util/jar/JarFile.getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;
|
Компиляция AOT делает эвристические решения, выбирая методы которые могут в будущем сократить время запуска. Поэтому возможно, что при последующих запусках приложения AOT-компиляции будут подвергнуты некоторые дополнительные методы.
Скомпилированный метод AOT также может быть подвергнут компиляции JIT, если он соответствует необходимым критериям перекомпиляции. Однако для компиляции AOT выбираются методы, необходимые при запуске приложения, а компиляция JIT оптимизирует часто используемые методы, поэтому возможно скомпилированные методы AOT впоследствии не будут вызываться настолько часто, чтобы подвергнуться компиляции JIT.
В листинге 6 представлен фрагмент вывода, генерируемого при выполнении теста производительности SPECjbb2005 с параметром
-Xjit:verbose.
В этом фрагменте содержится информация об AOT-компиляции двух методов:
com/ibm/security/util/ObjectIdentifier.equals
и java/math/BigDecimal.multiply.
Первый метод не подвергается JIT-компиляции в то время, как чаще используемый метод
java/math/BigDecimal.multiply
компилируется в режиме JIT дважды, достигая уровня оптимизации «hot».
В тесте SPECjbb2005 нет длинной фазы запуска, поэтому AOT-компиляции подвергаются лишь несколько методов. Заметьте, что компиляция AOT осуществляется с уровнем оптимизации «cold», так как общей целью AOT является ускорение запуска приложения.
Листинг 6. Информация о выполняемой оптимизации, выводимая при использовании параметра
-Xjit:verbose
+ (AOT cold) com/ibm/security/util/ObjectIdentifier.equals(Ljava/lang/Object;)
Storing AOT code for ROMMethod 0x118B8AF4 in shared cache... Succeeded.
+ (AOT cold) java/math/BigDecimal.multiply(Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
Storing AOT code for ROMMethod 0x119D3C60 in shared cache... Succeeded.
+ (warm) java/math/BigDecimal.multiply(Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
+ (hot) java/math/BigDecimal.multiply(Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
|
В статистике использования совместно используемых классов, получаемой с помощью команды
java
-Xshareclasses:printAllStats
перечислены все скомпилированные AOT методы и все помещенные в кэш совместно используемые классы. Используя сводные данные, можно понять, правильно ли задан размер кэша совместно используемых классов. Например, в листинге 7 показано, что кэш общим размером 16776844 байта был использован только на 40 процентов и что 5950936 байт были заняты 1668 ROM-классами, а 683772 байта были заняты 458 скомпилированными AOT методами:
Листинг 7. Подробная информация о кэше
base address = 0x424DE000 end address = 0x434D0000 allocation pointer = 0x4295E748 cache size = 16776844 free bytes = 9971656 ROMClass bytes = 5950936 AOT bytes = 683772 Data bytes = 57428 Metadata bytes = 113052 Metadata % used = 1% # ROMClasses = 1668 # AOT Methods = 458 # Classpaths = 7 # URLs = 0 # Tokens = 0 # Stale classes = 0 % Stale classes = 0% Cache is 40% full |
В листинге 8 приведены данные, выводимые при выполнении команды
-Xshareclasses:destroyAll,
и говорящие о том, что кэши были очищены. Также при выполнении команды выводится сообщение
Could not
create the Java virtual machine ,
на которое не нужно обращать внимания.
Листинг 8. Очистка кэшей
Attempting to destroy all caches in cacheDir C:\...\javasharedresources\
JVMSHRC256I Persistent shared cache "eclipse" has been destroyed
Could not create the Java virtual machine.
|
Измерение использования памяти
Существует множество инструментов измерения производительности, с помощью которых можно видеть преимущества работы с памятью при использовании совместно используемых классов. Конкретный инструмент, который можно порекомендовать, зависит от используемой операционной системы. При изучении использования памяти необходимо помнить о том, что кэш реализуется посредством отображаемого в память файла, который позволяет совместно использовать свое содержимое различным виртуальным машинам. Каждый инструмент анализа используемой памяти должен уметь различать совместно используемую память (которую могут использовать несколько виртуальных Java-машин) и частную память (которую может использовать лишь одна виртуальная машина).
Программа Virtual Address Dump Utility (Windows)
Программа Virtual Address Dump (vadump) – это инструмент от Microsoft®,
входящий в набор утилит для управления ресурсами. Его можно использовать для получения информации об использовании памяти приложением, или, в нашем случае, о кэше совместно используемых классов. Vadump генерирует много информации, но нам из нее интересен только размер рабочего пространства процесса, который позволит получить представление об использовании памяти приложением. Для отображения рабочего пространства процесса служит команда
vadump -os -p <pid>
.
В выводимых сводных данных представлено множество информации об используемой процессом памяти. Чтобы оценить эффект, достигаемый благодаря использованию совместно используемых классов, обратите внимание на изменение показателя Grand Total Working Set (общий размер рабочего пространства процесса) и его составляющих Private (частная память), Shareable (память, доступная для совместного использования) и Shared (совместно используемая память). В листинге 9 показан пример выводимой vadump сводной информации. Совместно используемые классы реализованы как отображаемые в память файлы, поэтому занимаемая ими память показана в строке Mapped Data.
Листинг 9. Пример выводимой vadump информации
vadump -os -p 5364
Category Total Private Shareable Shared
Pages KBytes KBytes KBytes KBytes
Page Table Pages 29 116 116 0 0
Other System 8 32 32 0 0
Code/StaticData 2079 8316 5328 140 2848
Heap 87 348 348 0 0
Stack 4 16 16 0 0
Teb 1 4 4 0 0
Mapped Data 95 380 0 24 356
Other Data 61 244 240 4 0
Total Modules 2079 8316 5328 140 2848
Total Dynamic Data 248 992 608 28 356
Total System 37 148 148 0 0
Grand Total Working Set 2364 9456 6084 168 3204
|
Идентификатор процесса, необходимый команде vadump, можно определить с помощью диспетчера задач Windows:
- Откройте диспетчер задач и выберите в нем вкладку Processes (Процессы).
- Найдите столбец с именем PID. (Если столбец не показан, откройте через View > Select Columns (Вид > Выбрать столбцы) меню, показанное на рисунке 2, и включите в нем отображение идентификатора процесса).
- Найдите имя процесса, который вы хотите отлаживать и запомните соответствующее ему значение в столбце PID. Это и есть идентификатор процесса, который необходимо передать в vadump.
Рисунок 2. Включение отображения идентификатора процесса в диспетчере задач
Анализ использования памяти в Linux с помощью top
В распоряжении пользователей Linux есть множество инструментов для анализа использования памяти. Чтобы оценить эффект от использования совместно используемых классов хорошо подходит команда
top.
Для облегчения чтения выводимого командой текста, мы передадим команде ID процесса и запустим ее в командном (batch) режиме. В листинге 10 показана выполняемая команда и пример выводимых ею данных:
Листинг 10. Команда top и результат её выполнения
top -b -n 1 -p <pid>
top - 13:33:41 up 18 days, 9:30, 1 user, load average: 0.00, 0.00, 0.00
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0% us, 0.0% sy, 0.0% ni, 100.0% id, 0.0% wa, 0.0% hi, 0.0% si
Mem: 8157972k total, 311312k used, 7846660k free, 56448k buffers
Swap: 2104472k total, 0k used, 2104472k free, 141956k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
7073 root 15 0 41616 13m 2228 S 0.0 0.2 5:43.70 X
|
Для нас наибольший интерес представляют следующие значения:
- VIRT — виртуальный образ (КБ):
общее количество виртуальной памяти, используемой процессом. В него входит весь код, данные, совместно используемые библиотеки, а также страницы памяти, размещенные в файле подкачки.
- RES — размер резидентной памяти (КБ): количество используемой процессом физической памяти без учета памяти, размещенной в файле подкачки.
- SHR — размер совместно используемой памяти (КБ): количество используемой процессом совместно используемой памяти. Оно отражает память, которая потенциально может быть использована совместно с другими процессами.
Настройка Eclipse для использования возможностей совместно используемых классов
Для иллюстрации улучшений, которых можно достичь, мы будем измерять эффект от применения совместно используемых классов в двух реальных приложениях: среде Eclipse, как представителе клиентских настольных приложений, и Apache Tomcat, как представителе серверных приложений.
Как мы уже замечали в начале статьи, в настоящее время не существует отдельного установочного пакета с IBM SDK для Java 6 и средой выполнения Java для Windows. Если вы используете Windows (а не Linux или AIX), вам необходимо загрузить специальный установочный пакет для Eclipse, содержащий все необходимое.
Если вы используете Linux или AIX, загрузите отдельно IBM SDK для Java 6, а затем загрузите нужную вам версию Eclipse с сайта проекта Eclipse (см. Ресурсы). Далее, следуя инструкциям установки, настройте Eclipse для использования IBM SDK для Java 6.
После установки Eclipse необходимо выполнить следующие дополнительные шаги:
- Включите использование совместно используемых классов для модулей расширения. Установите адаптер модулей расширения OSGI
(см. Ресурсы) в директорию модулей расширения Eclipse.
- Загрузите файл SampleView.jar
(см. секцию Загрузки)
и установите его в директорию модулей расширения Eclipse. Этот модуль расширения помогает измерить время запуска Eclipse. Он подсоединяется к трассировкам виртуальной Java-машины от IBM и после инициализации представления выводит информацию о некоторых точках трассировки. Об использовании трассировок виртуальной Java-машины IBM для получения статистики о времени запуска приложения мы расскажем в следующем разделе.
- Создайте два рабочих пространства с именами workspace1 и workspace2. Это позволит запускать два экземпляра Eclipse, использующих различные рабочие пространства, но работающих с одним кэшем классов.
Также вам необходимо установить Tomcat, если вы еще этого не сделали. Просто загрузите приложение с Web-сайта Apache Tomcat, распакуйте архив и следуйте инструкциям, содержащимся в файле running.txt.
Используя инструменты и приложения, описанные в предыдущем разделе, мы измерим, какой выигрыш в производительности может обеспечить применение совместно используемых классов. Для облегчения интерпретации результатов мы будем по возможности изолировать функциональность совместно используемых классов (отключая, там, где возможно, другие возможности приложений).
Производительность Eclipse: использование памяти
Для измерения использования памяти мы одновременно запустили на одной системе Windows несколько экземпляров Eclipse, использующих разные рабочие пространства. Мы будем сравнивать данные, собранные с помощью vadump для экземпляров Eclipse, запущенных в трех различных режимах:
- Eclipse, запущенный в обычном режиме, без использования совместно используемых классов.
- Eclipse, впервые запущенный с использованием совместно используемых классов (с чистым кэшем).
- Второй экземпляр Eclipse, запущенный с использованием того же самого кэша совместно используемых классов.
Для включения совместно используемых классов в Eclipse необходимо создать команду запуска с правильными параметрами виртуальной Java-машины. Мы будем использовать для запуска Eclipse bat-файл, показанный в листинге 11. Он выполняет следующие функции:
- Принимает один параметр командной строки со значением 1 или 2, обозначающий одно из двух созданных ранее рабочих пространств Eclipse.
- Очищает все существующие кэши совместно используемых классов, если используется пространство workspace1.
- После завершения работы Eclipse выводит статистику использования кэшей.
Листинг 11. Bat-файл, используемый для запуска Eclipse
@echo off
rem batch file to start Eclipse using the specified workspace
SET ECLIPSE_HOME=C:\java\eclipse\IBMEclipse\eclipse
SET JVM=C:\java\eclipse\IBMEclipse\ibm_sdk50\jre\bin\java.exe
SET WNAME=C:\java\eclipse\workspace%1
SET SC_OPTS=-Xshareclasses:name=eclipse,verbose
SET VMARGS=%SC_OPTS%
echo Clearing shared classes cache
if %1==1 %JVM% -Xshareclasses:destroyAll
echo JVM version
%JVM% -version
echo Starting Eclipse
%ECLIPSE_HOME%\eclipse.exe -nosplash -data %WNAME% -vm %JVM% -vmargs %VMARGS%
%JVM% -Xshareclasses:name=eclipse,printStats
|
В листинге 12 приведены данные, полученные с помощью vadump для экземпляра Eclipse, не использующего совместно используемые классы. Наибольший интерес в этих данных для нас представляют показатели Shareable (КБ), Shared (КБ) и Grand Total Working Set: (КБ)
Листинг 12. Результат работы vadump для экземпляра Eclipse, не использующего совместно используемые классы
Category Total Private Shareable Shared
Pages KBytes KBytes KBytes KBytes
Page Table Pages 54 216 216 0 0
Other System 28 112 112 0 0
Code/StaticData 4199 16796 11500 1052 4244
Heap 9400 37600 37600 0 0
Stack 98 392 392 0 0
Teb 21 84 84 0 0
Mapped Data 130 520 0 36 484
Other Data 5337 21348 21344 4 0
Total Modules 4199 16796 11500 1052 4244
Total Dynamic Data 14986 59944 59420 40 484
Total System 82 328 328 0 0
Grand Total Working Set 19267 77068 71248 1092 4728
|
В листинге 13 показаны данные, полученные с помощью vadump для экземпляра Eclipse, запущенного с помощью bat файла из листинга 11. Можно заметить, что примерно 4 МБ классов (4116 КБ в столбце Shareable и строке Mapped Data) были помещены в кэш, что привело к соответствующему увеличению общего размера рабочей области. Эта память может быть использована совместно с другими процессами. При сравнении данных, получаемых с помощью vadump необходимо помнить о том, что для каждого запуска приложения цифры могут немного отличаться.
Листинг 13. Результат работы vadump и статистика для Eclipse, запущенного первый раз с использованием кэша совместно используемых классов
Category Total Private Shareable Shared
Pages KBytes KBytes KBytes KBytes
Page Table Pages 54 216 216 0 0
Other System 28 112 112 0 0
Code/StaticData 4256 17024 11676 1072 4276
Heap 8631 34524 34524 0 0
Stack 103 412 412 0 0
Teb 20 80 80 0 0
Mapped Data 1155 4620 0 4116 504
Other Data 5386 21544 21540 4 0
Total Modules 4256 17024 11676 1072 4276
Total Dynamic Data 15295 61180 56556 4120 504
Total System 82 328 328 0 0
Grand Total Working Set 19633 78532 68560 5192 4780
Current statistics for cache "eclipse":
base address = 0x42B0E000
end address = 0x43B00000
allocation pointer = 0x42E0B958
cache size = 16776844
free bytes = 12005976
ROMClass bytes = 4001256
AOT bytes = 625428
Data bytes = 57043
Metadata bytes = 87141
Metadata % used = 1%
# ROMClasses = 1334
# AOT Methods = 480
# Classpaths = 4
# URLs = 0
# Tokens = 0
# Stale classes = 0
% Stale classes = 0%
|
Теперь запустим еще один экземпляр Eclipse с совместным использованием классов и выполним для него программу vadump. Глядя на результаты её работы, сначала кажется, что использование памяти изменилось очень незначительно. Однако при более внимательном изучении можно заметить, что здесь 4 МБ памяти (4564 КБ в столбце Shared Mapped Data) используются совместно с другим процессом. Vadump (и диспетчер задач) учитывают совместно используемую память в строке Grand Total Working Set для каждого процесса, который ее использует. Поэтому второй экземпляр Eclipse использует на 4 МБ меньше памяти за счет совместного использования кэша классов, созданного и заполненного первым экземпляром Eclipse.
Здесь приведены данные для запуска Eclipse с минимальным набором установленных модулей расширения. Логично ожидать, что при большем количестве установленных модулей расширения в кэш будет помещаться больше классов и процедура загрузки приложения будет еще больше оптимизирована.
Листинг 14. Результат работы vadump для второго экземпляра Eclipse, запущенного с использованием существующего кэша совместно используемых классов
Category Total Private
Shareable Shared
Pages KBytes KBytes KBytes KBytes
Page Table Pages 54 216 216 0 0
Other System 29 116 116 0 0
Code/StaticData 4254 17016 11676 0 5340
Heap 8684 34736 34736 0 0
Stack 98 392 392 0 0
Teb 20 80 80 0 0
Mapped Data 1150 4600 0 36 4564
Other Data 5261 21044 21040 4 0
Total Modules 4254 17016 11676 0 5340
Total Dynamic Data 15213 60852 56248 40 4564
Total System 83 332 332 0 0
Grand Total Working Set 19550 78200 68256 40 9904
|
Производительность Eclipse: время загрузки
Наряду с улучшением работы с памятью, совместное использование классов также сокращает время загрузки, так как классы загружаются из кэша, а не с диска, а также за счет использования скомпилированного AOT кода. Для измерения времени загрузки Eclipse мы применим специальное представление (описанное ранее в этой статье), использующее трассировки виртуальной Java-машины для вывода сообщений о загрузке. Также мы изменим bat-файл запуска Eclipse из листинга 11, добавив в него включение трассировок Java-машины и запись следующих событий трассировки:
- Инициализация трассировки:
Трассировка запускается практически сразу после старта Java-машины до загрузки классов. При измерениях мы будем считать этот момент началом запуска приложения.
- Образцы сообщений представления: Первое такое сообщение записывается при инициализации представления, сигнализируя о том, что Eclipse запустился. При измерениях мы будем считать этот момент окончанием запуска приложения.
В листинге 15 показан измененный bat-файл, в котором выделены жирным шрифтом строки, конфигурирующие трассировку виртуальной Java-машины:
Листинг 15. Bat-файл для запуска Eclipse с включенными трассировками
@echo off
rem batch file to time Eclipse startup
SET ECLIPSE_HOME=C:\java\eclipse\IBMEclipse\eclipse
SET WNAME=C:\java\eclipse\workspace%1
SET JVM=C:\java\eclipse\IBMEclipse\ibm_sdk60\jre\bin\java.exe
SET TRACE_OPTS=-Xtrace:iprint=tpnid{j9trc.0},iprint=SampleView
SET SC_OPTS=-Xshareclasses:name=eclipse,verbose
SET VMARGS=%SC_OPTS% %TRACE_OPTS%
echo Clearing shared classes cache
if %1==1 %JVM% -Xshareclasses:destroyAll
echo JVM version
%JVM% -version
echo VM arguments
echo %VMARGS%
echo Starting Eclipse
%ECLIPSE_HOME%\eclipse.exe -nosplash -data %WNAME% -vm %JVM% -vmargs %VMARGS%
%JVM% -Xshareclasses:name=eclipse,printStats
|
В листингах 16 и 17 показаны данные, выводимые при запуске Eclipse без совместно используемых классов и с ними соответственно. Как можно видеть, применение совместно используемых классов сокращает время запуска приложения примерно на 1 секунду или на 25 процентов. Данные в листинге 17 приведены для второго запуска Eclipse с применением совместно используемых классов, так как первый запуск уходит на заполнение кэша классов. При работе с исходной версией Eclipse (с минимумом модулей расширения) в кэше хранится только 4 МБ данных. Так что для больших и сложных приложений на базе Eclipse применение совместно используемых классов открывает большие возможности для сокращения времени запуска.
Листинг 16. Загрузка Eclipse без совместного использования классов
09:47:55.296*0x41471300 j9trc.0 - Trace initialized for VM = 00096238 09:47:59.500 0x41471300SampleView.2 - Event id 1, text = Mark 09:47:59.500 0x41471300SampleView.0 > Entering getElements(Object parent) 09:47:59.500 0x41471300SampleView.1 < Exiting getElements(Object parent) Startup = 4.204 seconds |
Листинг 17. Загрузка Eclipse с совместным использованием классов
09:30:40.171*0x41471300 j9trc.0 - Trace initialized for VM = 000962A8
[-Xshareclasses verbose output enabled]
JVMSHRC158I Successfully created shared class cache "eclipse"
JVMSHRC166I Attached to cache "eclipse", size=16777176 bytes
09:30:43.484 0x41471300SampleView.2 - Event id 1, text = Mark
09:30:43.484 0x41471300SampleView.0 > Entering getElements(Object
parent) 09:30:43.484 0x41471300SampleView.1 < Exiting
getElements(Object parent)
Startup = 3.313 seconds
|
Производительность Tomcat: использование памяти
Мы уже видели, как использование совместно используемых классов обеспечивает сокращение времени загрузки и количества используемой памяти в клиентских приложениях. Эти преимущества имеют место и в серверной среде. Как отмечалось ранее, в качестве примера серверного приложения мы будем использовать Tomcat. Tomcat не требует выполнения каких-либо специальных действий для использования виртуальной Java-машины от IBM. Для включения использования совместно используемых классов необходимо лишь задать правильные значения в переменной среды JVM_OPTS, как показано в листинге 18. Tomcat использует значения из этой переменной в качестве параметров командной строки для запуска виртуальной Java-машин
Листинг 18. Установка параметров Java-машины для Tomcat
export JAVA_OPTS="-Xmx32m -Xms32m -Xshareclasses:name=tomcat,verbose"
|
Чтобы продемонстрировать эффект от использования совместно используемых классов на различных платформах, мы использовали Linux-версии Java-машины от IBM и Tomcat.
Как говорилось ранее, в Linux хорошим инструментом измерения использования процессом (Tomcat) памяти является команда
top.
В этом примере мы сначала запустили Tomcat без использования совместно используемых классов (удалив строку "-Xshareclasses:name=tomcat,verbose" из переменной среды JVM_OPTS) и выполнили для него команду
top,
а затем повторили то же самое с использованием совместно используемых классов. Затем мы запустили второй экземпляр Tomcat, чтобы продемонстрировать разницу в использовании памяти двумя процессами, совместно работающими с одним и тем же кэшем классов. В листингах 19, 20 и 21 приведен результат работы команды
top
для каждого из этих случаев. В листинге 22 показана соответствующая статистика кэша совместно используемых классов.
Листинг 19. Использование памяти процессом Tomcat без применения совместно используемых классов
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie Cpu(s): 0.1% us, 0.0% sy, 0.0% ni, 99.9% id, 0.0% wa, 0.0% hi, 0.0% si Mem: 8157972k total, 1727072k used, 6430900k free, 101152k buffers Swap: 2104472k total, 0k used, 2104472k free, 1370944k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 24595 jbench 25 0 66744 54m 8400 S 0.0 0.7 0:03.71 java |
Листинг 20. Использование памяти процессом Tomcat с применением совместно используемых классов
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie Cpu(s): 0.0% us, 0.0% sy, 0.0% ni, 99.9% id, 0.1% wa, 0.0% hi, 0.0% si Mem: 8157972k total, 1728800k used, 6429172k free, 101152k buffers Swap: 2104472k total, 0k used, 2104472k free, 1376084k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 24621 jbench 17 0 78440 56m 14m S 0.0 0.7 0:04.04 java |
Листинг 21. Использование памяти двумя экземплярами Tomcat, работающими с одним кэшем совместно используемых классов
Tasks: 2 total, 0 running, 2 sleeping, 0 stopped, 0 zombie Cpu(s): 0.0% us, 0.0% sy, 0.0% ni, 100.0% id, 0.0% wa, 0.0% hi, 0.0% si Mem: 8157972k total, 1766440k used, 6391532k free, 101152k buffers Swap: 2104472k total, 0k used, 2104472k free, 1376084k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 24621 jbench 17 0 78440 56m 14m S 0.0 0.7 0:04.08 java 24674 jbench 16 0 77600 51m 14m S 0.0 0.6 0:02.28 java |
Листинг 22. Статистика использования кэшей Tomcat
base address = 0x76D0E000 end address = 0x77D00000 allocation pointer = 0x77186268 cache size = 16776852 free bytes = 10085680 ROMClass bytes = 5911028 AOT bytes = 621280 Data bytes = 57051 Metadata bytes = 101813 Metadata % used = 1% # ROMClasses = 1634 # AOT Methods = 452 # Classpaths = 6 # URLs = 0 # Tokens = 0 # Stale classes = 0 % Stale classes = 0% Cache is 39% full |
Сравним данные об использовании памяти сервером Tomcat с применением совместно используемых классов и без них. С первого взгляда сложно разглядеть преимущества использования совместно используемых классов, так как с ними, судя по цифрам, количество используемой памяти возрастает. Однако если немного углубиться в цифры, картина начинает меняться:
- SHR увеличилась на 6 МБ, с 8400 КБ до 14 МБ. Эта цифра показывает размер данных, хранящихся в кэше совместно используемых классов.
- RES немного увеличилась (с 54 МБ до 56 МБ) из-за инфраструктуры, необходимой для поддержки совместно используемых классов (библиотеки объектов и т.д.).
- VIRT увеличилась, так как эта цифра отражает суммарное значение увеличившихся SHR и RES
Выполнив программу
top
для второго экземпляра Tomcat (процесс 24674 в листинге 21), можно видеть, что процесс использует то же количество совместно используемой памяти (14 МБ в SHR), но размер используемой RES памяти уменьшился на 5 МБ (с 56 МБ до 51 МБ); также уменьшился объем используемой виртуальной памяти. Программа
top,
как и vadump в Windows, правильно идентифицирует память, имеющую возможности для совместного использования, но не отображает другие процессы, использующие эту же память. В нашем случае оба экземпляра Tomcat используют один кэш совместно используемых классов, поэтому общее количество используемой ими памяти сокращается. В этом тесте серверы Tomcat меньше половины общего размера кэша. Согласно данным из листинга 22 в кэш было помещено 5911028 байт данных из совместно используемых ROM-классов (чуть меньше 6 МБ). Это означает, что еще остаются возможности для оптимизации использования памяти посредством организации совместного использования классов из кэша.
Производительность Tomcat: время запуска
Использование совместно используемых классов также позволяет сократить время запуска Tomcat. Для измерения времени запуска мы будем использовать данные, записываемые в файл журнала catalina.out, находящийся в директории <TOMCAT_HOME>/logs. Чтобы получить базовое значение для дальнейших сравнений, мы запустим Tomcat без совместного использования классов. В листинге 23 показано время запуска Tomcat (для ясности мы пропустили остальные строки, записанные в журнал в процессе запуска):
Листинг 23. Время запуска Tomcat без использования совместно используемых классов
24-Apr-2008 13:01:08 org.apache.catalina.startup.Catalina start INFO: Server startup in 1138 ms |
Сравним эти данные со временем запуска Tomcat с применением совместно используемых классов, показанным в листинге 24.
Листинг 24. Время запуска Tomcat с применением совместно используемых классов и скомпилированного AOT -кода
24-Apr-2008 13:06:57 org.apache.catalina.startup.Catalina
start INFO: Server startup in 851 ms
|
Как видно, использование совместно используемых классов сокращает время запуска Tomcat с1138 мс до 851 мс, что составляет 25 процентов. Это улучшение было достигнуто благодаря использованию совместно используемых классов и предварительно скомпилированного AOT-кода. Чтобы увидеть, какой выигрыш обеспечивает использование AOT-кода, можно выключить его использование с помощью параметра командной строки -Xnoaot и снова замерить время запуска. Результат показан в листинге 25:
Листинг 25. Увеличение времени запуска при отключении использования AOT-кода
24-Apr-2008 13:03:50 org.apache.catalina.startup.Catalina
start INFO: Server startup in 950 ms
|
Как видно из листинга 25, время запуска заметно увеличилось. Это говорит о том, что возможность размещения AOT-кода в кэше совместно используемых классов обеспечивает существенный выигрыш во времени загрузки Tomcat.
В этой статье показано, как использование совместно используемых классов может сократить время запуска ваших Java-приложений и уменьшить количество используемой памяти. В частности, мы продемонстрировали, как можно измерить выигрыш в количестве используемой памяти и времени запуска, получаемый от применения совместно используемых классов в приложениях Eclipse и Tomcat. Конечно же, не все приложения работают так же, а значит, не для всех наблюдается такой же положительный результат. Однако даже в простой конфигурации, которую мы использовали, было отмечено значительное сокращение времени запуска.
Помните, что наибольший выигрыш можно получить, запуская несколько приложений работающих на одном уровне SDK IBM, так как они обладают максимальным количеством кода, доступного для совместного использования. Но даже для одного приложения можно получить выигрыш во времени загрузки, если настроить в нем работу с кэшем совместно используемых классов.
Также мы продемонстрировали, как можно отслеживать совместно используемую память с помощью таких инструментов, как vadump в Windows и top в Linux, чтобы точно измерять экономию памяти за счет совместного использования классов. Хотя информация, получаемая с помощью этих инструментов, не дает четкой картины использования памяти, мы показали, как читать ее между строк.
| Описание | Имя | Размер | Метод загрузки |
|---|---|---|---|
| Исходный код примера | j-sharedclasses.jar | 6KБ | HTTP |
Научиться
- Ознакомьтесь с оригиналом статьи:
"Enhance performance with class sharing"
(EN, developerWorks, сентябрь 2008 г.).
-
"
Технология Java в стиле IBM: совместно используемые классы
"
(EN, Бен Корри, developerWorks, май 2006 г.): введение в совместно исполльзуемые классы в Java 5.
-
"В статье
Java в реальном времени, часть 1: использование языка Java в системах реального времени"
(EN, Марк Студли, Майк Фултон, Майкл Доусон, Райан Сайемпекон, Джон Какур,
developerWorks, апрель 2007 г.) рассказывается об использовании AOT-кода в Java-системах реального времени.
- В статье
"
Не забывайте о памяти
"
(EN, Эмма Шеферд, Мартин Троттер, Кэролайн Мэйнард, Мэтью Питерс,
developerWorks, апрель 2004 г.) рассказывается о приемах мониторинга использования памяти приложениями Java
в операционной системе Windows (в том числе и об использовании утилиты vadump).
Получить продукты и технологии
-
Набор инструментов разработки для
платформы Java от IBM: (EN)
загрузите пакет инструментов создания и тестирования апплетов и приложений для одной из
самых популярных в IBM платформ - Java 2, Standard Edition.
- Загрузите среду разработки Eclipse для
Windows с предустановленной
JVM от IBM (EN).
- Загрузите Web-сервер Tomcat (EN).
- Посетите сайт
проекта Eclipse (EN).
- Загрузите утилиту
vadump
для Windows 2003 (EN).
Обсудить

Адам Пилкингтон работает аналитиком производительности Java в центре Java-технологий IBM и занимается производительностью сервера приложений WebSphere в Java 6. До прихода в IBM в 2006 году он работал архитектором J2EE-решений в крупной британской организации финансового сектора. Имеет ученую степень по математике и компьютерным наукам.

Грэхем Роусон руководит командой, занимающейся анализом производительности Java в лаборатории IBM в Хёрсли, Англия. Грэхем работает в IBM на различных должностях уже 24 года, занимаясь поддержкой сервера транзакций CICS и технологии Java, разработанных в Хёрсли. Грэхем имеет степень бакалавра химических наук университета восточной Англии (г. Норвич, Англия) и сертификат разработчика ПО, полученный в Оксфордском университете (Англия).