Java как запустить программу с параметрами
В этом уроке мы будем обрабатывать аргументы командной строки на Java. Мы получим к ним доступ и прочитаем их, а также сопоставим аргументы с типами данных, чтобы изменить поток кода.
Вступление
Аргументы (параметры) командной строки-это строки текста, используемые для передачи дополнительной информации программе при запуске приложения через интерфейс командной строки (CLI) операционной системы.
В этом уроке мы будем обращаться к аргументам (параметрам), переданным в основной метод Java-приложения, и читать их. Мы также сопоставим их с различными типами данных, чтобы мы могли обрабатывать их и изменять поток кода на основе входных данных.
Доступ к Аргументам Командной Строки
Точкой входа для каждой программы Java является метод main() :
Аргументы, переданные программе при ее инициализации, хранятся в массиве args . Кроме того, Java также поддерживает vararg в этом месте:
Тем не менее, мы можем легко получить доступ к каждому аргументу, переданному в этот метод. Давайте начнем с того, что распечатаем их один за другим:
Затем мы скомпилируем этот файл .java :
После чего мы сможем запустить его:
Сопоставление аргументов с типами данных
Сами аргументы представляют собой массив строк. Так что на самом деле все, что мы передаем, – это Строка. Тем не менее, мы также можем конвертировать строки в различные типы данных:
Давайте сделаем это:
Теперь давайте снова скомпилируем код:
А затем давайте запустим его без каких-либо аргументов:
Git Essentials
Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!
Нас встречают с:
Если мы приведем аргументы:
Установка аргументов в IDE
Это предполагает, что вы запускаете код через командную строку, что не всегда так. Большинство людей используют IDE для работы над своими проектами, в которых вместо этого есть удобная кнопка “Запустить”.
К счастью, вы можете указать IDE передать эти аргументы в вызов run. Вот примеры того, как вы можете сделать это с помощью некоторых популярных идей:
Затмение
В разделе “Выполнить” -> “Конфигурации запуска” :
IntelliJ
В разделе “Выполнить” -> “Редактировать конфигурации” :
Вывод
В этой статье мы рассмотрели, как мы можем получить доступ к аргументам командной строки, передаваемым в приложение Java при его запуске.
Затем мы сопоставили переданные аргументы с различными типами данных и обработали их соответствующим образом. Имея это в виду, легко создавать простые инструменты CLI и изменять поток кода на основе переданных аргументов.
6 полезных инструментов командной строки, которые должен знать Java-разработчик
Агрегатор статистики Java (jstat)
Агрегатор статистики Java или команду jstat используют, когда возникает проблема со скоростью запуска приложения. Это служебный инструмент, который отображает статистику производительности Java Virtual Machine (JVM). Используйте его, когда вам нужно узнать размер динамической памяти или алгоритм автоматической сборки мусора JRE.
Диспетчер зависимостей Java (jdeps)
Jdeps — это анализатор зависимостей классов Java, полезный для быстрого определения статических зависимостей приложений и библиотек. Jdeps также предлагает варианты более новой и предпочтительной API, которую вы можете использовать для замены существующей версии.
Компилятор Java (javac)
Javac — один из самых полезных инструментов командной строки для разработчиков. Он входит в состав JDK из первой версии и включен в сборку многих популярных IDE. Javac используется для устранения любых проблем, возникающих в процессе сборки и развертывания проекта.
Профилировщик Java (javap)
Он отменяет компиляцию, разделяет файлы классов и показывает, что внутри них. Его также называют дизассемблером (Java Class File Disassembler). Вы можете использовать Javap, чтобы узнать, как работает конкретный оператор Java. Также с помощью профилировщика вы можете увидеть методы, доступные в классе, если у вас нет доступа к исходному коду.
Утилита архивирования Java (jar)
Утилита Java Archive или команда jar используется для создания сжатого архивного файла. Это еще один полезный инструмент, которым регулярно пользуются многие разработчики. Jar также может пригодиться, когда вы хотите сравнить одну версию выпуска кода с другой или указать точку входа для встроенных JAR-файлов или других исполняемых файлов.
Инструмент состояния процесса JVM (jps)
Этот инструмент работает независимо от операционной системы и предоставляет удобный способ определения идентификатора процесса (PID). Предположим, вы запустили программу и хотите связать ее с Java Memory-Map (jmap). Для того чтобы сделать это, нужен PID, и именно здесь на помощь приходит jps. В большинстве случаев полезно применять комбинацию флагов «-mlv». Она распечатывает аргументы основного метода, аргументы полных имен пакетов, передаваемые JVM.
Заключение
Это лишь несколько из полезных инструментов командной строки, которые должен знать каждый Java-разработчик. Со временем почти все инструменты, применяемые в создании софта на Java, значительно улучшились. Но многие разработчики все еще полагаются на командную строку для решения важных задач. Кроме того, если вы новичок, всегда рекомендуется иметь хотя бы базовые представления о популярных инструментах командной строки. Это поможет вам не только в учебе, но и в карьере.
Сейчас уже никто не создает программы в консоли. Используя любимую IDE, разработчик чувствует себя неуютно за чужим компьютером, где её нет.
Решив разобраться в работе Ant и Maven, я поймал себя на том, что не смогу собрать приложение без них в консоли.
В данной статье я постарался уместить все этапы проектирования демонстрационного приложения, чтобы не искать справку по каждой команде на просторах Интернета.
От простого к .
Каждая программа обычно содержится в отдельном каталоге. Я придерживаюсь правила создавать в этом каталоге по крайней мере две папки: src и bin. В первой содержатся исходные коды, во второй — результат компиляции. В данных папках будет структура каталогов, зависящая от пакетов.
Один файл
Можно сделать и без лишних папок.
Берем сам файл HelloWorld.java.
Переходим в каталог, где лежит данный файл, и выполняем команды.
В данной папке появится файл HelloWorld.class. Значит программа скомпилирована. Чтобы запустить
Отделяем бинарные файлы от исходников
Теперь сделаем тоже самое, но с каталогами. Создадим каталог HelloWorld и в нем две папки src и bin.
Компилируем
Здесь мы указали, что бинарные файлы будут сохраняться в отдельную папку bin и не путаться с исходниками.
Используем пакеты
А то, вдруг, программа перестанет быть просто HelloWorld-ом. Пакетам лучше давать понятное и уникальное имя. Это позволит добавить данную программу в другой проект без конфликта имен. Прочитав некоторые статьи, можно подумать, что для имени пакета обязательно нужен домен. Это не так. Домены — это удобный способ добиться уникальности. Если своего домена нет, воспользуйтесь аккаунтом на сайте (например, ru.habrahabr.mylogin). Он будет уникальным. Учтите, что имена пакетов должны быть в нижнем регистре. И избегайте использования спецсимволов. Проблемы возникают из-за разных платформ и файловых систем.
Поместим наш класс в пакет с именем com.qwertovsky.helloworld. Для этого добавим в начало файла строчку
В каталоге src создадим дополнительные каталоги, чтобы путь к файлу выглядел так: src/com/qwertovsky/helloworld/HelloWorld.java.
Компилируем
В каталоге bin автоматически создастся структура каталогов как и в src.
Если в программе несколько файлов
HelloWorld.java
Adder.java
Ошибка возникла из-за того, что для компиляции нужны файлы с исходными кодами классов, которые используются (класс Calculator). Надо указать компилятору каталог с файлами с помощью ключа -sourcepath.
Компилируем
Если удивляет результат
Есть возможность запустить отладчик. Для этого существует jdb.
Сначала компилируем с ключом -g, чтобы у отладчика была информация.
Отладчик запускает свой внутренний терминал для ввода команд. Справку по последним можно вывести с помощью команды help.
Указываем точку прерывания на 9 строке в классе Calculator
Запускаем на выполнение.
Чтобы соориентироваться можно вывести кусок исходного кода, где в данный момент находится курссор.
Узнаем, что из себя представляет переменная а.
Выполним код в текущей строке и увидим, что sum стала равняться 2.
Поднимемся из класса Adder в вызвавший его класс Calculator.
Удаляем точку прерывания
Можно избежать захода в методы, используя команду next.
Проверяем значение выражения и завершаем выполнение.
Хорошо бы протестировать
Запускаем. В качестве разделителя нескольких путей в classpath в Windows используется ';', в Linux — ':'. В консоли Cygwin не работают оба разделителя. Возможно, должен работать ';', но он воспринимается как разделитель команд.
Создадим библиотеку
Класс Calculator оказался полезным и может быть использован во многих проектах. Перенесем всё, что касается класса Calculator в отдельный проект.
Измените также назавания пакетов в исходных текстах. В HelloWorld.java нужно будет добавить строку
Делаем архив jar
С помощью ключа -C мы запустили программу в каталоге bin.
Надо узнать, что у библиотеки внутри
Можно распаковать архив zip-распаковщиком и посмотреть, какие классы есть в библиотеке.
Информацию о любом классе можно получить с помощью дизассемблера javap.
Из результата видно, что класс содержит кроме пустого конструктора, ещё один метод sum, внутри которого в цикле вызывается метод add класса Adder. По завершении метода sum, вызывается Adder.getSum().
Без ключа -c программа выдаст только список переменных и методов (если использовать -private, то всех).
Лучше снабдить библиотеку документацией
Изменим для этого класс калькулятора.
Документацию можно создать следующей командой. При ошибке программа выдаст список возможных опций.
В результате получиться следующее
Можно подписать jar-архив
Если требуется подписать свою библиотеку цифровой подписью, на помощь придут keytool и jarsigner.
Генерируем подпись.
Генерируем Certificate Signing Request (CSR)
Содержимое полученного файла отправляем в центр сертификации. От центра сертификации получаем сертификат. Сохраняем его в файле (например, qwertokey.cer) и импортируем в хранилище
Файл qwertokey.cer отправляем всем, кто хочет проверить архив. Проверяется он так
Использование библиотеки
Есть программа HelloWorld, которая использует библиотечный класс Calculator. Чтобы скомпилировать и запустить программу, нужно присоединить библиотеку.
Компилируем
Собираем программу
Это можно сделать по-разному.
Первый способ
Здесь есть тонкости.
В строке
не должно быть пробелов в конце.
Вторая тонкость описана в [3]: в этой же строке должен стоять перенос на следующую строку. Это если манифест помещается в архив сторонним архиватором.
Программа jar не включит в манифест последнюю строку из манифеста, если в конце не стоит перенос строки.
Ещё момент: в манифесте не должно быть пустых строк между строками. Будет выдана ошибка «java.io.IOException: invalid manifest format».
При использовании команды echo надо следить только за пробелом в конце строки с main-class.
Второй способ
В данном способе избегаем ошибки с пробелом в main-class.
Третий способ
Включили код нужной библиотеки в исполняемый файл.
Запуск исполняемого jar-файла
Файл calculator.jar исполняемым не является. А вот helloworld.jar можно запустить.
Если архив был создан первыми двумя способами, то рядом с ним в одном каталоге должна находится папка lib с файлом calculator.jar. Такие ограничения из-за того, что в манифесте в class-path указан путь относительно исполняемого файла.
При использовании третьего способа нужные библиотеки включаются в исполняемый файл. Держать рядом нужные библиотеки не требуется. Запускается аналогично.
Как быть с приложениями JavaEE
Аналогично. Только библиотеки для компиляции нужно брать у сервера приложений, который используется. Если я использую JBoss, то для компиляции сервлета мне нужно будет выполнить примерно следующее
Структура архива JavaEE-приложения должна соответствовать определенному формату. Например
Способы запуска приложения на самом сервере с помощью командной строки для каждого сервера различны.
Надеюсь, данная статья станет для кого-нибудь шпаргалкой для работы с Java в командной строке. Данные навыки помогут понять содержание и смысл Ant-скриптов и ответить на собеседовании на более каверзные вопросы, чем «Какая IDE Вам больше нравится?».
Пусть исходный файл HelloUniverse.java содержит определение класса и статичный метод main , который выводит в терминал одну строку текста:
Обычно для запуска этого класса требуется сначала скомпилировать его с помощью Java-компилятора (javac), который создаст файл HelloUniverse.class:
Затем нужно с помощью команды виртуальной машины Java (интерпретатора) запустить получившийся файл:
Тогда сначала запустится виртуалка, которая загрузит класс и исполнит код.
А если вам нужно быстро проверить фрагмент кода? Или вы новичок в Java (в данном случае это ключевой момент) и хотите поэкспериментировать с языком? Описанные два этапа могут всё усложнить.
В Java SE 11 можно напрямую запускать одиночные исходные файлы без промежуточной компиляции.
Эта возможность особенно полезна для новичков, которые хотят поработать с простыми программами. В сочетании с jshell получается прекрасный набор инструментов для обучения начинающих.
Профессионалы могут с помощью этих инструментов изучать нововведения в языке или тестировать незнакомые API. На наш взгляд, лучше автоматизировать многие задачи, вроде написания Java-программ в виде скриптов с последующим исполнением из оболочки ОС. В результате мы можем гибко работать с shell-скриптами и пользоваться всеми возможностями Java. Поговорим об этом подробнее во второй части статьи.
Эта прекрасная возможность Java 11 позволяет напрямую исполнять одиночный исходный файл без компилирования. Давайте обсудим.
Что вам потребуется
Для запуска кода, приведённого в статье, вам понадобится версия Java не ниже 11. На момент написания статьи текущим релизом был Java SE Development Kit 12.0.1 — финальная версия находится здесь, достаточно принять условия лицензии и кликнуть на ссылку для вашей ОС. Если хотите поэкспериментировать с самыми свежими возможностями, то можете скачать JDK 13 early access.
Обратите внимание, что сейчас также доступны релизы OpenJDK разных вендоров, в том числе AdoptOpenJDK.
В этой статье мы будем вместо Java IDE применять обычный текстовый редактор, чтобы избежать всякой магии IDE, и использовать командную строку Java прямо в терминале.
Запускаем .java с помощью Java
Функция JEP 330 (запуск однофайловых программ с исходным кодом) появилась в JDK 11. Она позволяет напрямую исполнять исходные файлы с исходным Java-кодом, без использования интерпретатора. Исходный код компилируется в памяти, а затем исполняется интерпретатором без создания на диске .class-файла.
Однако эта функция ограничена кодом, который хранится в одном файле. Вы не можете исполнять сразу несколько исходных файлов.
Чтобы обойти это ограничение, все классы нужно определять в одном файле. Никаких ограничений на их количество нет. Кроме того, пока они находятся в одном файле, не имеет значения, являются ли они публичными или частными.
Первый класс, определённый в файле, будет считаться основным, и в него нужно поместить метод main. То есть важна очерёдность.
Первый пример
Начнём с классического простейшего примера — Hello Universe!
Мы продемонстрируем описываемую возможность на разных примерах, чтобы вы получили представление, как можно её использовать в повседневном программировании.
Создайте файл HelloUniverse.java с кодом из начала статьи, скомпилируйте и запустите получившийся class-файл. Затем удалите его, сейчас поймёте зачем:
Если теперь с помощью Java-интерпретатора вы запустите class-файл без компиляции:
то увидите тот же результат: файл будет исполнен.
То есть под капотом всё же выполняется компиляция. И в случае её ошибки мы получим уведомление об этом. Можете проверить структуру директорий и убедиться, что class-файл не генерируется, компиляция выполняется в памяти.
Теперь давайте разберёмся, как это всё устроено.
Как интерпретатор Java выполняет программу HelloUniverse
В JDK 10 модуль запуска Java может работать в трёх режимах:
- Исполнение class-файла.
- Исполнение основного класса из JAR-файла.
- Исполнение основного класса модуля.
- Исполнение класса, объявленного в исходном файле.
Система определяет ваше намерение ввести исходный файл по двум признакам:
- Первый элемент в командной строке не является ни опцией, ни частью опции.
- В строке может присутствовать опция --source .
Во втором случае выбирается режим работы с исходным файлом, и первый элемент в командной строке, который не является опцией, считается исходным файлом, который нужно скомпилировать и запустить.
Если файл не имеет расширения .java, то нужно использовать опцию --source , чтобы принудительно перейти в режим работы с исходным файлом.
Это важно в случаях, когда исходный файл представляет из себя «скрипт», который нужно выполнить, а имя файла не соответствует обычным соглашениям о наименованиях исходных файлов с Java-кодом.
С помощью опции --source можно определять версию языка исходника. Об этом мы поговорим ниже.
Можно ли передавать в командной строке аргументы?
Давайте расширим нашу программу Hello Universe, чтобы она выводила персональное приветствие любому пользователю, зашедшему на InfoQ Universe:
Сохраним код в файле Greater.java. Обратите внимание, что имя файла не соответствует имени публичного класса. Это нарушает правила спецификации Java.
Как видите, совершенно не важно, что имена класса и файла не совпадают. Внимательный читатель мог также заметить, что мы передали в код аргументы после обработки имени файла. Это означает, что любой аргумент в командной строке, идущий после имени файла, передаётся стандартному основному методу.
Определяем уровень исходного кода с помощью опции --source
Есть два сценария использования опции --source :
- Определение уровня исходного кода.
- Принудительный перевод runtime-среды Java в режим работы с исходным файлом.
Давайте сначала рассмотрим второй сценарий. Переименуем Greater.java просто в greater без расширения и попробуем выполнить:
При отсутствии расширения .java интерпретатор команд ищет скомпилированный класс по имени, переданному в виде аргумента — это первый режим работы модуля запуска Java. Чтобы это не происходило, воспользуемся опцией --source для принудительного переключения в режим работы с исходным файлом:
Теперь перейдём к первому сценарию. Класс Greater.java совместим с JDK 10, поскольку содержит ключевое слово var , но не совместим с JDK 9. Изменим source на 10 :
Снова запустим предыдущую команду, но в этот раз передадим --source 9 вместо 10 :
Обратите внимание: компилятор предупреждает о том, что var стала в JDK 10 ограниченным именем типа. Но поскольку у нас язык уровня 10, компиляция продолжается. Однако возникает сбой, потому что в исходном файле нет типа с именем var .
Всё просто. Теперь рассмотрим использование нескольких классов.
Работает ли этот подход с несколькими классами?
Рассмотрим пример с двумя классами. Код проверяет, является ли заданное строковое значение палиндромом.
Вот код, сохранённый в файле PalindromeChecker.java:
Запустим снова, подставив «RaceCar» вместо «MadAm»:
Теперь подставим «Mohamed» вместо «RaceCar»:
Как видите, можно добавлять в один исходный файл сколько угодно публичных классов. Следите только за тем, чтобы основной метод был определён первым. Интерпретатор будет использовать первый класс в качестве стартовой точки для запуска программы после компилирования кода в памяти.
Можно использовать модули?
Да, никаких ограничений. Скомпилированный в памяти код запускается как часть безымянного модуля с опцией --add-modules=ALL-DEFAULT , которая даёт доступ ко всем модулям, поставляемым с JDK.
То есть код может использовать разные модули без необходимости явно определять зависимости с помощью module-info.java.
Запустим программу и получим результат:
Теперь вы можете быстро тестировать новые фичи, предоставляемые разными модулями, не создавая свой собственный модуль.
Почему скрипты так важны в Java?
Сначала давайте вспомним, что такое скрипты:
Скрипт — это программа, написанная для определённого runtime-окружения, которая автоматизирует исполнение задач или команд, которые человек может исполнять поочерёдно.
Из этого общего определения мы можем вывести простое определение скриптового языка — это язык программирования, использующий высокоуровневые конструкции для интерпретации и исполнения по одной команде (или команд) за раз.
Скриптовый язык использует серии команд, записанных в файле. Часто такие языки являются интерпретируемыми (а не компилируемыми) и придерживающимися процедурного стиля программирования (хотя некоторые скриптовые языки также обладают свойствами объектно-ориентированных языков).
В целом скриптовые языки легче в освоении и быстрее в наборе кода по сравнению с более структурированными компилируемыми языками вроде Java, C и С++. К серверным скриптовым языкам относятся Perl, PHP и Python, а на клиентской стороне — JavaScript.
Долгое время Java считался хорошо структурированным, сильно типизированным компилируемым языком, который интерпретируется виртуальной машиной для выполнения на любой вычислительной архитектуре. Однако Java не так прост в изучении и прототипировании по сравнению с другими скриптовыми языками.
Тем не менее, Java уже исполнилось 24 года, его использует около 10 млн разработчиков по всему миру. В последних релизах добавили ряд новых возможностей, чтобы молодым программистам было легче изучать этот язык, а также чтобы пользоваться функциями языка и API без компилирования и IDE. Например, в Java SE 9 появился инструмент JShell (REPL), который поддерживает интерактивное программирование.
А с выходом JDK 11 этот язык получил возможность поддержки скриптов, поскольку теперь вы можете исполнять код с помощью простого вызова команды java !
В Java 11 есть два основных способа использования скриптов:
- Прямой вызов команды java .
- Применение *nix-скриптов для командной строки, аналогичных Bash-скриптам.
Shebang-файлы: запускаем Java как shell-скрипт
Итак, в Java SE 11 появилась поддержка скриптов, включая традиционные shebang-файлы из мира *nix. Для их поддержки не потребовалось спецификации языка.
Запустим следующий пример в терминале, работающем под macOS Mojave 10.14.5. Но сначала определим важные правила, которым нужно следовать при создании shebang-файла:
Сохраним код в файл с именем dirlist без расширения, а затем пометим его как исполняемый: mohamed_taman:code$ chmod +x dirlist .
Запустим снова с помощью команды, которая передаёт родительскую директорию, и проверим результат.
Примечание: при оценке исходного кода интерпретатор игнорирует shebang-строку (первую строку). Таким образом, shebang-файл можно явно вызвать с помощью модуля запуска, например, с дополнительными опциями:
Также нужно отметить: если скриптовый файл лежит в текущей директории, то вы можете выполнить его так:
А если скрипт лежит в директории, путь которой указан в пользовательском PATH, то выполнить его можно так:
И в завершение дам несколько советов, о чём нужно помнить при использовании скриптов.
Советы
- Некоторые опции, которые вы будете передавать в javac, могут не передаться (или не распознаться) java , например, опции -processor или -Werror .
- Если в classpath есть файлы .class и .java, то модуль запуска заставит вас использовать class-файл.
Обратите внимание на два файла java.java в пакете HelloUniverse и файл HelloUniverse.java в той же директории. Если вы попробуете выполнить:
Резюме
Начиная с Java SE 11 и впервые в истории программирования вы можете напрямую исполнять скрипты с Java-кодом без компилирования. Это позволяет писать скрипты на Java и исполнять их из *nix-командной строки.
С каждым днем слово java все больше и больше воспринимается уже не как язык, а как платформа благодаря небезызвестному invokeDynamic. Именно поэтому сегодня я бы хотел поговорить про виртуальную java машину, а именно — об так называемых Performance опциях в Oracle HotSpot JVM версии 1.6 и выше (server). Потому что сегодня почти не встретить людей, которые знают что-то больше чем -Xmx, -Xms и -Xss. В свое время, когда я начал углубляться в тему, то обнаружил огромное количество интересной информации, которой и хочу поделится. Отправной точкой, понятное дело, послужила официальная документация от Oracle. А дальше — гугл, эксперименты и общение:
-XX:+DoEscapeAnalysis
Начну, пожалуй, с самой интересной опции — DoEscapeAnalysis. Как многие из Вас знают, примитивы и ссылки на объекты создаются не в куче, а выделяются на стеке потока (256КБ по умолчанию для Hotspot). Вполне очевидно, что язык java не позволяет создавать объекты на стеке на прямую. Но это вполне себе может проделывать Ваша JVM 1.6 начиная с 14 апдейта.
Про то, как работает сам алгоритм можно прочитать тут (PDF). Если коротко, то:
- Если область видимости объекта не выходит за область метода, в котором он создается, то такой объект может быть создан на фрейме стека вместо кучи (на самом деле не сам объект, а его поля, на совокупность которых заменяется объект);
- Если объект не покидает область видимости потока, то к такому объекту другие потоки не имеют доступа и следовательно все операции синхронизации над объектом могут быть удалены.
Для реализации данного алгоритма строится и используется так называемый — граф связей (connection graph), по которому на этапе анализа (алгоритмов анализа — несколько) осуществляется проход для нахождения пересечений с другими потоками и методами.
Таким образом после прохода графа связей для любого объекта возможно одно из следующих следующих состояний:
- GlobalEscape — объект доступен из других потоков и из других методов, например статическое поле.
- ArgEscape — объект был передан как аргумент или на него есть ссылка из объекта аргумента, но сам он не выходит из области видимости потока в котором был создан.
- NoEscape — объект не покидает область видимости метода и его создание может быть вынесено на стек.
После этапа анализа, уже сама JVM проводит возможную оптимизацию: в случае если объект NoEscape, то он может быть создан на стеке; если объект NoEscape или ArgEscape, то операции синхронизации над ним могут быть удалены.
Следует уточнить, что на стеке создается не сам объект а его поля. Так как JVM заменяет цельный объект на совокупность его полей (спасибо Walrus за уточнение).
Вполне очевидно, что благодаря такого рода анализу, производительность отдельных частей программы может возрасти в разы. В синтетических тестах, на подобии этого:
Кстати, EscapeAnalysis как раз частично ответственен за известный спор про StringBuilder и StringBuffer. То есть, если Вы вдруг в методе использовали StringBuffer вместо StringBuilder, то EscapeAnalysis (в случае срабатывания) устранит блокировки для StringBuffer'а, после чего StringBuffer вполне превращается в StringBuilder.
-XX:+AggressiveOpts
Опция AggressiveOpts является супер опцией. Не в том плане, что она резко увеличивает производительность Вашего приложения, а в том смысле, что она всего лишь изменяет значения других опций (на самом деле, это не совсем так — в исходном коде JDK довольно не мало мест, где AggressiveOpts изменяет поведение JVM, помимо упомянутых опций, один из примеров тут). Проверять измененные флаги будем с помощью двух команд:
После выполнения разница в результатах выполнения команд выглядела так:
-AggressiveOpts | +AggressiveOpts | |
---|---|---|
AutoBoxCacheMax | 128 | 20000 |
BiasedLockingStartupDelay | 4000 | 500 |
EliminateAutoBox | false | true |
OptimizeFill | false | true |
OptimizeStringConcat | false | true |
Иными словами, все что делает эта опция — изменяет 5 данных параметров виртуальной машины. Причём, для версий 1.6 update 35 и 1.7 update 7 никаких отличий замечено не было. Данная опция по умолчанию отключена и в клиентском моде ничего не изменяет.
Расcмотрим, что же java подразумевает под агрессивной оптимизацией:
-XX:AutoBoxCacheMax=size
Позволяет расширить диапазон кешируемых значений для целых типов при старте виртуальной машины. Эту опцию я уже упоминал тут (второй абзац).
-XX:BiasedLockingStartupDelay=delay
Как известно, synchronized блок в java может быть представлен одним из 3-х видов блокировок:
Так как большинство объектов (синхронизированных) блокируются максимум 1 одним потоком, то такие объекты могут быть привязаны (biased) к этому потоку и операции синхронизации над этим объектом внутри потока сильно удешевляются. Если к biased объекту пытается получить доступ другой поток, то происходит переключение блокировки для этого объекта на thin блокировку.
Само переключение относительно дорого, поэтому на старте JVM существует задержка, которая по умолчанию создает все блокировки как thin и если никакой конкуренции не обнаружено и код используется одним и тем же потоком, то такие блокировки, после истечения задержки, становятся biased. То есть, JVM пытается на старте определить сценарии использования блокировок и соответственно использует меньше переключений между ними. Соответственно, выставляя BiasedLockingStartupDelay в ноль, мы рассчитаем на то, что основные куски кода синхронизации будут использоваться лишь одни и тем же потоком.
-XX:+OptimizeStringConcat
Тоже довольно интересная опция. Распознает паттерн на подобии
и вместо постоянного выделения памяти под новую операцию конкатенации, идет попытка вычислить общее количество символов каждого объекта конкатенации для выделения памяти только 1 раз.
Иными словами, если мы вызовем 20 раз операцию append() для строки длинной 20 символов. То создание массива char произойдет один раз и длиной 400 символов.
XX:+OptimizeFill
Циклы заполнения/копирования массивов заменяются на прямые машинные инструкции для ускорения работы.
Например, следующий блок (взято из Arrays.fill()):
будет полностью замен на соответствующие процессорные инструкции на подобии сишных memset, memcpy только более низкоуровневых.
XX:+EliminateAutoBox
Исходя из названия, флаг должен как-то уменьшать количество операций автобоксинга. К сожалению, я до конца так и не смог выяснить, что же делает этот флаг. Единственное, что удалось прояснить, что применяется это только к Integer оболочкам.
-XX:+UseCompressedStrings
Довольно спорная опция по моему убеждению… Если в далеких 90-х разработчики java не пожалели 2 байта на символ, то сегодня такая оптимизация смотрится довольно нелепо. Если кто не догадался, то опция заменяет в строках символьные массивы на байтовые, где это возможно (ASCII). По сути:
Таким образом возможна существенная экономия памяти. Но в виду того, что меняется тип, появляются накладные расходы на контроль типов при определенных операциях. То есть, с этой опцией возможна деградация производительности JVM. Собственно поэтому опция по умолчанию и отключена.
-XX:+UseStringCache
Довольно загадочная опция, судя по названию она должна каким-то образом кешировать строки. Как? Не понятно. Информации нету. Да и по коду похоже она ничего не выполняет. Буду рад, если кто-то сможет прояснить.
-XX:+UseCompressedOops
Для начала несколько фактов:
- Размер указателя на объект в 32-х разрядной JVM составляет 32 бита. В 64-х разрядной — 64 бита. Следовательно, в первом случае Вы можете использовать адресное пространство размером 2^32 байт (4 ГБ), а во втором случае 2^64 байт.
- Размер объектов в java кратен 8 байтам не зависимо от разрядности виртуальной машины (это не для всех виртуальных машин правда, но речь о Hotspot). То есть, при использовании 32-х разрядных указателей последние 3 бита будут всегда нулями, фактически, виртуальная машина реально использует лишь 29 бит.
Данная опция позволяет уменьшить размер указателя для 64-х разрядных JVM до 32-х бит, но в этом случае размер кучи ограничен 4 ГБ, поэтому, в дополнение к сокращенному указателю, используется свойство о кратности 8 байтам. В результате получаем возможность использовать адресное пространство размером 2^35 байт (32 ГБ) имея указатели в 32 бита.
Фактически, внутри виртуальной машины, мы имеем указатели на объекты, а не конкретные байты в памяти. Понятное дело, что из-за подобных допущений (о кратности) появляются дополнительные расходы на преобразование указателей. Но по сути это всего лишь одна операция сдвига и суммирования.
Помимо уменьшения размеров самих указателей, эта опция уменьшает также заголовки объектов и разного рода выравнивания и сдвиги внутри созданных объектов, что позволяет в среднем уменьшить потребление памяти на 20-60% в зависимости от модели приложения.
То есть, из недостатков имеем лишь:
- Максимальный размер кучи ограничен 32 ГБ (64ГБ для JRockit при кратности объектов 16 байтам);
- Появляются доп. расходы на преобразование JVM ссылок в нативные и обратно.
Так как для большинства приложений опция несет одни плюсы, то начиная с JDK 6 update 23 она включена по умолчанию, так же как и в JDK 7. Детальней тут и тут.
-XX:+EliminateLocks
Опция, которая устраняет лишние блокировки путем их объединения. Например следующие блоки:
будут преобразованы соответственно в
Таким образом сокращается количество попыток захвата монитора.
Заключение
За бортом осталось довольно много интересных опций, так как поместить все ~700 флагов в одну статью довольно трудно. Я специально не затрагивал опции по тюнингу сборщика, так это довольно обширная и сложная тема и она заслуживает нескольких постов. Надеюсь статья была вам полезной.
Читайте также: