Улучшаем производительность приложений с помощью совместно используемых классов Java

Изучаем новейшие возможности совместного использования классов в JRE от IBM

В последней версии JRE для Java™ SE 6 от IBM ® была расширена функциональность совместно используемых классов, впервые появившаяся в 5-й версии. В этой статье специалисты по анализу производительности Адам Пилкингтон и Грэхем Роусон расскажут подробно о новых возможностях, включающих в себя сокращение времени запуска приложений и улучшение использования памяти.

Адам Пилкингтон, инженер-программист, IBM

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



Грэхем Роусон, инженер-программист, IBM

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



22.09.2011

Расширения инфраструктуры совместно используемых классов, впервые появившейся в 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. Пример структуры кэша совместно используемых классов
Cache composition

Узнайте подробнее о коде AOT.

Сжатие классов

Для наиболее эффективной работы с кэшем совместно используемых классов виртуальная Java-машина использует технологию сжатия, увеличивающую количество классов, которые можно хранить в кэше. Сжатие классов выполняется автоматически и не может быть изменено с помощью параметров командной строки.

Постоянные кэши

В JRE от IBM для Java 5 кэш совместно используемых классов был реализован с помощью сегментов совместно используемой памяти, что позволяло виртуальным Java-машинам совместно использовать один и тот же кэш, но этот кэш очищался при перезагрузке операционной системы. Это значило, что первая Java-машина, запущенная после перезагрузки, должна была создавать кэш заново. В Java 6 реализация кэша по умолчанию использует отображающийся в память файл, что позволяет сохранять кэш при перезагрузке операционной системы.

Подробнее об AOT

Компилятор AOT – это дополнительный механизм компиляции, появившийся в JRE IBM для Java 6. В предыдущих версиях JRE IBM метод мог быть выполнен либо интерпретацией всех байт-кодов Java, составляющего этот метод, либо как машинный код, скомпилированный и оптимизированный JIT (Just-in-Time) компилятором, входящим в JRE. JIT компилирует код динамически при фактическом вызове метода, при этом применяемые приемы компиляции определяются в процессе анализа метода во время выполнения.

Что такое код AOT?

Код 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

Чтобы понять, какие методы в вашем приложении были подвергнуты 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:

  1. Откройте диспетчер задач и выберите в нем вкладку Processes (Процессы).
  2. Найдите столбец с именем PID. (Если столбец не показан, откройте через View > Select Columns (Вид > Выбрать столбцы) меню, показанное на рисунке 2, и включите в нем отображение идентификатора процесса).
  3. Найдите имя процесса, который вы хотите отлаживать и запомните соответствующее ему значение в столбце PID. Это и есть идентификатор процесса, который необходимо передать в vadump.
Рисунок 2. Включение отображения идентификатора процесса в диспетчере задач
Showing process ID information

Анализ использования памяти в 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 необходимо выполнить следующие дополнительные шаги:

  1. Включите использование совместно используемых классов для модулей расширения. Установите адаптер модулей расширения OSGI (см. Ресурсы) в директорию модулей расширения Eclipse.
  2. Загрузите файл SampleView.jar (см. секцию Загрузки) и установите его в директорию модулей расширения Eclipse. Этот модуль расширения помогает измерить время запуска Eclipse. Он подсоединяется к трассировкам виртуальной Java-машины от IBM и после инициализации представления выводит информацию о некоторых точках трассировки. Об использовании трассировок виртуальной Java-машины IBM для получения статистики о времени запуска приложения мы расскажем в следующем разделе.
  3. Создайте два рабочих пространства с именами 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.jar6KБ

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=760887
ArticleTitle=Улучшаем производительность приложений с помощью совместно используемых классов Java
publish-date=09222011