во что компилируется java
Руководство по Java 9: компиляция и запуск проекта
Авторизуйтесь
Руководство по Java 9: компиляция и запуск проекта
Установка Java 9
Сперва необходимо установить Java 9. Вы можете скачать её с сайта Oracle, но рекомендуется использовать SdkMAN!, так как в будущем он позволит вам с легкостью переключаться между разными версиями Java.
Можно установить SdkMAN! с помощью этой команды:
Посмотрите, какая сборка является последней:
Затем установите Java 9:
Теперь, если у вас установлены другие версии Java, вы можете переключаться между ними с помощью команды:
Компиляция и запуск «по-старому»
Для начала напишем какой-нибудь код, чтобы проверить наши инструменты. Если не использовать модульный дескриптор, то все выглядит так же, как и раньше.
Возьмем этот простой Java-класс:
Теперь, так как мы не использовали никаких особенностей Java 9, мы можем скомпилировать всё как обычно:
Теперь создадим библиотеку Greeting также без особенностей Java 9, чтобы посмотреть, как это работает.
Создадим файл greeting/ser/lib/Greeting.java со следующим кодом:
Изменим класс Main для использования нашей библиотеки:
Скомпилируем эту библиотеку:
Чтобы показать, как работают оригинальные Java-библиотеки, мы превратим эту библиотеку в jar-файл без дескрипторов модулей Java 9:
Просмотреть информацию о jar-файле можно с помощью опции tf :
Команда должна вывести:
Используем для этого cp (classpath):
И то же самое для запуска программы:
Мы можем упаковать приложение в jar-файл:
И затем запустить его:
Вот так выглядит структура нашего проекта на данный момент:
Модуляризация проекта
Пока что ничего нового, но давайте начнем модуляризацию нашего проекта. Для этого создадим модульный дескриптор (всегда называется module-info.java и размещается в корневой директории src/ ):
Команда для компиляции модуля в Java 9 отличается от того, что мы видели раньше. Использование старой команды с добавлением модуля к списку файлов приводит к ошибке:
Чтобы понять, почему наш код не компилируется, необходимо понять, что такое безымянные модули.
Любой класс, который загружается не из именованного модуля, автоматически выполняет часть безымянного модуля. В примере выше перед созданием модульного дескриптора наш код не был частью какого-либо модуля, следовательно, он был ассоциирован с безымянным модулем. Безымянный модуль — это механизм совместимости. Проще говоря, это позволяет разработчику использовать в приложениях Java 9 код, который не был модуляризирован. По этой причине код, относящийся к безымянному модулю, имеет правила сродни Java 8 и ранее: он может видеть все пакеты, экспортируемые из других модулей, и все пакеты безымянного модуля.
Модули в Java 9, за исключением неуловимого безымянного модуля описанного выше, должны объявлять, какие другие модули им необходимы. В случае с модулем com.app единственным требованием является библиотека Greeting. Но, как вы могли догадаться, эта библиотека (как и другие библиотеки, не поддерживающие Java 9) не является модулем Java 9. Как же нам включить её в проект?
В таком случае вам нужно знать имя jar-файла. Если у вас есть зависимость от библиотеки, которая не была конвертирована в модуль Java 9, вам надо знать, какой jar-файл вызывается для этой библиотеки, потому что Java 9 переведёт имя файла в валидный модуль.
Это называется автоматический модуль.
Так же, как и безымянные модули, автоматические модули могут читать из других модулей, и все их пакеты являются экспортируемыми. Но, в отличие от безымянных модулей, на автоматические можно ссылаться из явных модулей.
Для создания и использования app.jar в качестве исполняемого jar-файла выполните следующие команды:
Следующим шагом будет модуляризация библиотек, которые используются нашим приложением.
Модуляризация библиотек
Для модуляризации библиотеки нельзя сделать ничего лучше, чем использовать jdeps — инструмент для статистического анализа, который является частью Java SE.
Например, команда, которая позволяет увидеть зависимости нашей небольшой библиотеки, выглядит так:
А вот результат её выполнения:
Как и ожидалось, библиотека зависит только от java.base модуля.
Хорошо, но можно лучше. Мы можем попросить jdeps автоматически сгенерировать модульный дескриптор для набора jar-файлов. Просто укажите ему, куда сохранять сгенерированные файлы (например, в папку generated-mods ), и где находятся jar-файлы:
Команда создаст два файла: generated-mods/app/module-info.java и generated-mods/greetings/module-info.java со следующим содержимым:
Теперь мы можем добавить сгенерированный дескриптор для нашей библиотеки в её исходный код, переупаковать её, и у нас получится полностью модульное приложение:
Теперь у нас есть полностью модуляризированные библиотека и приложение. После удаления сгенерированных и бинарных файлов, структура нашего приложения выглядит следующим образом:
Обратите внимание, что для получения хороших данных от jdeps вы должны предоставить местоположение всех jar-файлов, которые используются в приложении, чтобы он мог составить полный граф модуля.
Наиболее простым способом получить список всех jar-файлов, которые используются в библиотеке, является использование скрипта Gradle. Он выведет пути локальных jar-файлов для всех зависимостей библиотек, которые вы добавите в секцию зависимостей, и скачает их, если необходимо:
Если у вас нет Gradle, вы можете использовать SdkMAN! для его установки:
Для получения списка зависимостей используйте следующую команду:
Полученную информацию передайте jdeps для анализа и автоматической генерации метаданных.
Это файл, который jdeps выводит для javaslang.match :
Создание собственного образа среды выполнения
С помощью jlink Java-приложения могут распространяться как образы, которые не требуют установки JVM.
Следующая команда создает образ для нашего com.app модуля без оптимизации, сжатия или отладочной информации:
Для запуска приложения используйте предоставляемый лаунчер в директории bin :
На этом всё. Разбор нововведений в Java 9 предлагаем прочитать в нашей статье.
Виды компиляции в JVM: сеанс черной магии с разоблачением
Сегодня вашему вниманию предлагается перевод статьи, в котором на примерах разобраны варианты компиляции в JVM. Особое внимание уделено AOT-компиляции, поддерживаемой в Java 9 и выше.
Приятного чтения!
Полагаю, любой, кому доводилось программировать на Java, слышал о мгновенной компиляции (JIT), а, возможно, и о компиляции перед выполнением (AOT). Кроме того, не приходится объяснять, что такое «интерпретируемые» языки. В этой статье будет рассказано, каким образом все эти возможности реализованы в виртуальной машине Java, JVM.
Чтобы байт-код Java выполнял какую-либо конкретную работу, есть 3 возможности заставить его это сделать:
Ради полноты картины упомяну, что существует и четвертый подход – напрямую интерпретировать исходный код, но в Java так не принято. Так делается, например, в Python.
Теперь давайте разберемся, как “java” работает в качестве (1) интерпретатора (2) JIT-компилятора и/или (3) AOT-компилятора – и когда.
Если коротко – как правило, “java” делает и (1), и (2). Начиная с Java 9 возможен и третий вариант.
Итак, если скомпилировать и запустить вышеприведенный код, то вывод будет вполне ожидаемым (конечно, значения истекшего времени у вас получатся другими):
А теперь вопрос: является ли этот вывод результатом работы “java” как интерпретатора, то есть, вариант (1), “java” как JIT-компилятора, то есть, вариант (2) либо он каким-то образом связан с AOT-компиляцией, то есть, вариант (3)? В этой статье я собираюсь найти верные ответы на все эти вопросы.
Первый ответ, который хочется дать – скорее всего, здесь имеет место только (1). Я говорю «скорее всего», так как не знаю, не установлена ли здесь какая-либо переменная окружения, которая бы изменяла опции JVM, заданные по умолчанию. Если ничего лишнего не установлено, и именно так “java” работает по умолчанию, то здесь мы 100% наблюдаем именно вариант (1), то есть, код полностью интерпретируемый. Я в этом уверен, так как:
(Ради удобочитаемости я буду писать каждую опцию с новой строки)
В данном случае должно быть совершенно понятно, что мы исключаем все функции (последняя *) во всех классах из всех пакетов, которые начинаются с java, jdk и sun (имена пакетов разделяются символом /, и можно использовать *). Команда quiet приказывает JVM не писать ничего об исключенных классах, поэтому в консоль будут выведены лишь те, которые сейчас скомпилируются. Итак, я запускаю:
Посмотрим, к чему же мы пришли. Теперь у нас есть простой код, где мы запускаем нашу функцию 10 раз. Ранее я упоминал, что эта функция интерпретируется, а не компилируется, поскольку так указано в документации, а теперь мы видим ее в логах (при этом, не видим в логах компиляции, и это означает, что JIT-компиляции она не подвергается). Отлично, вы только что видели инструмент “java” в действии, интерпретирующий и только интерпретирующий нашу функцию в 100% случаев. Итак, можем поставить галочку, что с вариантом (1) разобрались. Переходим к (2), динамической компиляции.
Вот вывод (напоминаю, у меня пропущены строки, касающиеся java.lang.invoke.MethodHandle ):
Приветствуем (hello!) динамически скомпилированную функцию Test.f или Test:: сразу после вызова номер 5, ведь я задал для CompileThreshold значение 5. JVM интерпретирует функцию 5 раз, затем компилирует ее и, наконец, запускает скомпилированную версию. Поскольку функция скомпилирована, она должна выполняться быстрее, но здесь мы этого проверить не можем, так как эта функция ничего не делает. Думаю, это хорошая тема для отдельного поста.
Вывод получится длинным, и там будут строки вроде:
Как видите, соответствующие функции компилируются в нативный машинный код.
Наконец, обсудим вариант 3, AOT. Компиляция перед выполнением, AOT, была недоступна в Java до версии 9.
В JDK 9 появился новый инструмент, jaotc – как понятно из названия, это AOT-компилятор для Java. Идея такова: запускаем компилятор Java “javac”, потом AOT-компилятор для Java “jaotc”, после чего запускаем JVM “java” как обычно. JVM в обычном порядке выполняет интерпретацию и JIT-компиляцию. Однако, если в функции есть AOT-скомпилированный код, она прямо его и использует, а не прибегает к интерпретации или JIT-компиляции. Поясню: вы не обязаны запускать AOT-компилятор, он опционален, а если вы им и воспользуетесь – то можете скомпилировать им до выполнения лишь те классы, которые хотите.
В нашем выводе, среди прочего, будет:
Как и в случае с соответствующей опцией “java”, в данном случае опцию можно сопровождать файлом, для этого существует опция –compile-commands к jaotc. В JEP 295 приводятся соответствующие примеры, которые я не буду здесь показывать.
Можно задать несколько AOT-библиотек, разделенных запятыми.
Перед выводом программы Test эта команда показывает следующее:
Как и планировалось, загружается библиотека AOT, и используются AOT-скомпилированные функции.
Если вам интересно, можете запустить следующую команду и проверить, происходит ли JIT-компиляция.
Как и предполагалось, JIT-компиляции не происходит, поскольку методы в классе Test скомпилированы до выполнения и предоставлены в виде библиотеки.
Возможен вопрос: если мы предоставляем нативный код функций, то как JVM определяет, не является ли нативный код устаревшим/несвежим? В качестве заключительного примера давайте модифицируем функцию f и зададим для a значение 6.
Я сделал этого лишь для того, чтобы изменить файл класса. Теперь мы заставим “javac” скомпилировать и запустить ту же команду, что и выше.
Как видите, я не запускал “jaotc” после “javac”, поэтому код из библиотеки AOT сейчас старый и некорректный, а у функции f значение a=5.
Вывод команды “java” выше демонстрирует:
Это означает, что функции в данном случае были динамически скомпилированы, поэтому код, полученный в результате AOT-компиляции, не использовался. Итак, обнаружено изменение в файле класса. Когда компиляция выполняется при помощи “javac”, ее отпечаток заносится в класс, а отпечаток класса также хранится в библиотеке AOT. Поскольку новый отпечаток класса отличается от того, что сохранен в библиотеке AOT, нативный код, скомпилированный заранее (AOT) не использовался. Вот и все, что я хотел рассказать о последнем варианте компиляции, до выполнения.
В этой статье я попытался объяснить и проиллюстрировать на простых реалистичных примерах, как JVM выполняет код Java: интерпретируя его, компилируя динамически (JIT) или заранее (AOT) – причем, последняя возможность появилась только в JDK 9. Надеюсь, вы узнали что-то новое.
SYNOPSIS
Arguments may be in any order.
DESCRIPTION
The javac tool reads class and interface definitions, written in the Java programming language, and compiles them into bytecode class files. It can also process annotations in Java source files and classes.
There are two ways to pass source code file names to javac:
You should arrange source files in a directory tree that reflects their package tree. For example, if you keep all your source files in C:\workspace, the source code for com.mysoft.mypack.MyClass should be in C:\workspace\com\mysoft\mypack\MyClass.java.
By default, the compiler puts each class file in the same directory as its source file. You can specify a separate destination directory with -d (see Options, below).
OPTIONS
The compiler has a set of standard options that are supported on the current development environment and will be supported in future releases. An additional set of non-standard options are specific to the current virtual machine and compiler implementations and are subject to change in the future. Non-standard options begin with -X.
Standard Options
If the -sourcepath option is not specified, the user class path is also searched for source files.
If the -processorpath option is not specified, the class path is also searched for annotation processors.
If -d is not specified, javac puts each class files in the same directory as the source file from which it was generated.
Note: The directory specified by -d is not automatically added to your user class path.
If you are cross-compiling (compiling classes against bootstrap and extension classes of a different Java platform implementation), this option specifies the directories that contain the extension classes. See Cross-Compilation Options for more information.
-g Generate all debugging information, including local variables. By default, only line number and source file information is generated. -g:none Do not generate any debugging information. -g: Generate only some kinds of debugging information, specified by a comma separated list of keywords. Valid keywords are: source Source file debugging information lines Line number debugging information vars Local variable debugging information -help Print a synopsis of standard options. -implicit: Controls the generation of class files for implicitly loaded source files. To automatically generate class files, use -implicit:class. To suppress class file generation, use -implicit:none. If this option is not specified, the default is to automatically generate class files. In this case, the compiler will issue a warning if any such class files are generated when also doing annotation processing. The warning will not be issued if this option is set explicitly. See Searching For Types. -Joption Pass option to the java launcher called by javac. For example, -J-Xms48m sets the startup memory to 48 megabytes. It is a common convention for -J to pass options to the underlying VM executing applications written in Java.
Note: CLASSPATH, -classpath, -bootclasspath, and -extdirs do not specify the classes used to run javac. Fiddling with the implementation of the compiler in this way is usually pointless and always risky. If you do need to do this, use the -J option to pass through options to the underlying java launcher.
Note: Classes found through the class path may be subject to automatic recompilation if their sources are also found. See Searching For Types.
-verbose Verbose output. This includes information about each class loaded and each source file compiled. -version Print version information. -Werror Terminate compilation if warnings occur. -X Display information about non-standard options and exit.
Cross-Compilation Options
By default, classes are compiled against the bootstrap and extension classes of the platform that javac shipped with. But javac also supports cross-compiling, where classes are compiled against a bootstrap and extension classes of a different Java platform implementation. It is important to use -bootclasspath and -extdirs when cross-compiling; see Cross-Compilation Example below.
-target version Generate class files that target a specified version of the VM. Class files will run on the specified target and on later versions, but not on earlier versions of the VM. Valid targets are 1.1, 1.2, 1.3, 1.4, 1.5 (also 5), 1.6 (also 6), and 1.7 (also 7).
The default for -target depends on the value of -source:
Non-Standard Options
Enable warning name with the option -Xlint:name, where name is one of the following warning names. Similarly, you can disable warning name with the option -Xlint:-name:
cast Warn about unnecessary and redundant casts. For example: classfile Warn about issues related to classfile contents. deprecation Warn about use of deprecated items. For example:
The method java.util.Date.getDay has been deprecated since JDK 1.1.
dep-ann Warn about items that are documented with an @deprecated Javadoc comment, but do not have a @Deprecated annotation. For example: divzero Warn about division by constant integer 0. For example: empty Warn about empty statements after if statements. For example: fallthrough Check switch blocks for fall-through cases and provide a warning message for any that are found. Fall-through cases are cases in a switch block, other than the last case in the block, whose code does not include a break statement, allowing code execution to «fall through» from that case to the next case. For example, the code following the case 1 label in this switch block does not end with a break statement:
If the -Xlint:fallthrough flag were used when compiling this code, the compiler would emit a warning about «possible fall-through into case,» along with the line number of the case in question.
finally Warn about finally clauses that cannot complete normally. For example:
options Warn about issues relating to the use of command line options. See Cross-Compilation Example for an example of this kind of warning. overrides Warn about issues regarding method overrides. For example, consider the following two classes:
The compiler generates a warning similar to the following:
path Warn about invalid path elements and nonexistent path directories on the command line (with regards to the class path, the source path, and other paths). Such warnings cannot be suppressed with the @SuppressWarnings annotation. For example: processing Warn about issues regarding annotation processing. The compiler generates this warning if you have a class that has an annotation, and you use an annotation processor that cannot handle that type of exception. For example, the following is a simple annotation processor:
Source file AnnoProc.java :
Source file AnnosWithoutProcessors.java :
rawtypes Warn about unchecked operations on raw types. The following statement generates a rawtypes warning:
The following does not generate a rawtypes warning:
serial Warn about missing serialVersionUID definitions on serializable classes. For example:
The compiler generates the following warning:
static Warn about issues relating to use of statics. For example:
The compiler generates the following warning:
To resolve this issue, you can call the static method m1 as follows:
try Warn about issues relating to use of try blocks, including try-with-resources statements. For example, a warning is generated for the following statement because the resource ac declared in the try statement is not used: unchecked Give more detail for unchecked conversion warnings that are mandated by the Java Language Specification. For example:
varargs Warn about unsafe usages of variable arguments (varargs) methods, in particular, those that contain non-reifiable arguments. For example:
The compiler generates the following warning for the definition of the method ArrayBuilder.addToList :
COMMAND LINE ARGUMENT FILES
An argument file can include javac options and source filenames in any combination. The arguments within a file can be space-separated or newline-separated. If a filename contains embedded spaces, put the whole filename in double quotes, and double each backslash ( «My Files\\Stuff.java» ).
When executing javac, pass in the path and name of each argument file with the ‘@‘ leading character. When javac encounters an argument beginning with the character `@‘, it expands the contents of that file into the argument list.
You could use a single argument file named » argfile » to hold all javac arguments:
This argument file could contain the contents of both files shown in the next example.
Create a file named » options » containing:
Create a file named » classes » containing:
You would then run javac with:
The argument files can have paths, but any filenames inside the files are relative to the current working directory (not path1 or path2 ):
ANNOTATION PROCESSING
javac provides direct support for annotation processing, superseding the need for the separate annotation processing tool, apt.
The API for annotation processors is defined in the javax.annotation.processing and javax.lang.model packages and subpackages.
Overview of annotation processing
Unless annotation processing is disabled with the -proc:none option, the compiler searches for any annotation processors that are available. The search path can be specified with the -processorpath option; if it is not given, the user class path is used. Processors are located by means of service provider-configuration files named META-INF/services/javax.annotation.processing.Processor on the search path. Such files should contain the names of any annotation processors to be used, listed one per line. Alternatively, processors can be specified explicitly, using the -processor option.
After scanning the source files and classes on the command line to determine what annotations are present, the compiler queries the processors to determine what annotations they process. When a match is found, the processor will be invoked. A processor may «claim» the annotations it processes, in which case no further attempt is made to find any processors for those annotations. Once all annotations have been claimed, the compiler does not look for additional processors.
If any processors generate any new source files, another round of annotation processing will occur: any newly generated source files will be scanned, and the annotations processed as before. Any processors invoked on previous rounds will also be invoked on all subsequent rounds. This continues until no new source files are generated.
After a round occurs where no new source files are generated, the annotation processors will be invoked one last time, to give them a chance to complete any work they may need to do. Finally, unless the -proc:only option is used, the compiler will compile the original and all the generated source files.
Implicitly loaded source files
SEARCHING FOR TYPES
When compiling a source file, the compiler often needs information about a type whose definition did not appear in the source files given on the command line. The compiler needs type information for every class or interface used, extended, or implemented in the source file. This includes classes and interfaces not explicitly mentioned in the source file but which provide information through inheritance.
For example, when you subclass java.applet.Applet, you are also using Applet’s ancestor classes: java.awt.Panel, java.awt.Container, java.awt.Component, and java.lang.Object.
When the compiler needs type information, it looks for a source file or class file which defines the type. The compiler searches for class files first in the bootstrap and extension classes, then in the user class path (which by default is the current directory). The user class path is defined by setting the CLASSPATH environment variable or by using the -classpath command line option. (For details, see Setting the Class Path).
You can specify different bootstrap or extension classes with the -bootclasspath and -extdirs options; see Cross-Compilation Options below.
The compiler may not discover the need for some type information until after annotation processing is complete. If the type information is found in a source file and no -implicit option is given, the compiler will give a warning that the file is being compiled without being subject to annotation processing. To disable the warning, either specify the file on the command line (so that it will be subject to annotation processing) or use the -implicit option to specify whether or not class files should be generated for such source files.
PROGRAMMATIC INTERFACE
javac supports the new Java Compiler API defined by the classes and interfaces in the javax.tools package.
Example
To perform a compilation using arguments as you would give on the command line, you can use the following:
This will write any diagnostics to the standard output stream, and return the exit code that javac would give when invoked from the command line.
You can use other methods on the javax.tools.JavaCompiler interface to handle diagnostics, control where files are read from and written to, and so on.
Old Interface
Note: This API is retained for backwards compatibility only; all new code should use the Java Compiler API, described above.
The com.sun.tools.javac.Main class provides two static methods to invoke the compiler from a program:
The args parameter represents any of the command line arguments that would normally be passed to the javac program and are outlined in the above Synopsis section.
The out parameter indicates where the compiler’s diagnostic output is directed.
The return value is equivalent to the exit value from javac.
Note that all other classes and methods found in a package whose name starts with com.sun.tools.javac (informally known as sub-packages of com.sun.tools.javac ) are strictly internal and subject to change at any time.
EXAMPLES
Compiling a Simple Program
The greetings directory is the package directory both for the source file and the class file and is off the current directory. This allows us to use the default user class path. It also makes it unnecessary to specify a separate destination directory with -d.
Compiling Multiple Source Files
Specifying a User Class Path
Having changed one of the source files in the previous example, we recompile it:
Since greetings.Hi refers to other classes in the greetings package, the compiler needs to find these other classes. The example above works, because our default user class path happens to be the directory containing the package directory. But suppose we want to recompile this file and not worry about which directory we’re in? Then we need to add \examples to the user class path. We can do this by setting CLASSPATH, but here we’ll use the -classpath option.
If we change greetings.Hi again, to use a banner utility, that utility also needs to be accessible through the user class path.
Separating Source Files and Class Files
It often makes sense to keep source files and class files in separate directories, especially on large projects. We use -d to indicate the separate class file destination. Since the source files are not in the user class path, we use -sourcepath to help the compiler find them.
Cross-Compilation Example
The following example uses javac to compile code that will run on a 1.6 VM.
You must specify the -bootclasspath option to specify the correct version of the bootstrap classes (the rt.jar library). If not, the compiler generates a warning:
If you do not specify the correct version of bootstrap classes, the compiler will use the old language rules (in this example, it will use version 1.6 of the Java programming language) combined with the new bootstrap classes, which can result in class files that do not work on the older platform (in this case, Java SE 6) because reference to non-existent methods can get included.