Запуск java приложения linux


Как сделать простым и понятным запуск Java-процессов в Linux / Docker / Хабр

По профилю работы DevOps-инженером я часто занимаюсь автоматизацией установки и настройки разнообразных IT-систем в различных средах: от контейнеров до облака. Приходилось работать со многими системами, основанными на Java-стеке: от небольших (вроде Tomcat), до масштабных (Hadoop, Cassandra и др.).

При этом почти каждая такая система, даже самая простая, почему-то имела сложную неповторимую систему запуска. Как минимум, это были многострочные shell-скрипты, как в Tomcat, а то и целые фреймворки, как в Hadoop. Мой нынешний "пациент" из этой серии, вдохновивший меня на написание этой статьи — хранилище артефактов Nexus OSS 3, скрипт запуска которого занимает ~400 строк кода.

Непрозрачность, избыточность, запутанность startup-скриптов создает проблемы даже при ручной установке одного компонента на локальной системе. А теперь представьте, что набор таких компонентов и сервисов нужно запаковать в Docker-контейнер, попутно написав еще один слой абстракции для мало-мальски адекватного оркестрирования, развернуть в Kubernetes-кластере и реализовать этот процесс в виде CI/CD-пайплайна. ..

Короче говоря, давайте на примере упомянутого Nexus 3 разберемся, как вернуться из лабиринта shell-скриптов к чему-то более похожему на java -jar <program.jar>, учитывая наличие удобных современных DevOps-инструментов.


Если в двух словах, то в древние времена, когда при упоминании UNIX не переспрашивали: "в смысле, Linux?", не было Systemd и Docker и др., для управления процессами использовались переносимые shell-скрипты (init-скрипты) и PID-файлы. Init-скрипты задавали необходимые настройки окружения, которые в разных UNIX-ах были свои, и, в зависимости от аргументов, запускали процесс или перезапускали/останавливали его с помощью ID из PID-файла. Подход простой и понятный, но эти скрипты переставали работать при каждой нестандартной ситуации, требуя ручного вмешательства, не позволяли запустить несколько копий процесса… но не суть.

Так вот, если внимательно посмотреть на упомянутые выше startup-скрипты в Java-проектах, то можно в них разглядеть явные признаки этого доисторического подхода, включая даже упоминания SunOS, HP-UX и других UNIX-ов. Как правило, такие скрипты делают примерно следующее:


  • используют синтаксис POSIX shell со всеми его костылями для UNIX/Linux-переносимости
  • определяют версию и релиз ОС через uname, /etc/*release и т.п.
  • ищут JRE/JDK в укромных уголках файловой системы и выбирают наиболее "подходящую" версию по хитрым правилам, иногда еще и специфичным для каждой ОС
  • рассчитывают числовые параметры JVM, например, размер памяти (-Xms, -Xmx), количество потоков GC и др.
  • оптимизируют JVM через -XX-параметры с учетом специфики выбранной версии JRE/JDK
  • отыскивают свои компоненты, библиотеки, пути к ним по окружающим директориям, конфигурационным файлам и т.п.
  • настраивают окружение: ulimits, переменные среды и т.п.
  • генерируют CLASSPATH циклом типа: for f in $path/*.jar; do CLASSPATH="${CLASSPATH}:$f"; done
  • парсят аргументы командной строки: start|stop|restart|reload|status|. ..
  • собирают Java-команду, которую в итоге нужно выполнить, из перечисленного выше
  • и, наконец, выполняют эту Java-команду. Зачастую при этом явно или неявно используются все те же пресловутые PID-файлы, &, nohup, специальные TCP-порты и прочие трюки из прошлого столетия (см. пример из Karaf)

Упомянутый скрипт запуска Nexus 3 — подходящий пример такого скрипта.

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


У — про — щать! KISS и YAGNI нам в руки. Тем более, что на дворе 2018-й год, а это значит, что:


  • за очень редким исключением, UNIX == Linux
  • задача управления процессами решена как для отдельного сервера (Systemd, Docker), так и для кластеров (Kubernetes и т.п.)
  • появилась куча удобных инструментов управления конфигурациями (Ansible и др.)
  • в администрирование пришла и уже основательно закрепилась тотальная автоматизация: вместо ручной настройки хрупких неповторимых "серверов-снежинок" теперь можно автоматически собирать унифицированные репродуцируемые виртуальные машины и контейнеры с помощью целого ряда удобных инструментов, включая упомянутые выше Ansible и Docker
  • повсеместно используются инструменты сбора runtime-статистики, как для самой JVM (пример), так и для Java-приложения (пример)
  • и, самое главное, появились специалисты: системные и DevOps-инженеры, которые умеют использовать перечисленные выше технологии и понимают, как правильно установить JVM на конкрентной системе и впоследствии подстроить ее с учетом собранной runtime-статистики

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


  • синтаксис POSIX shell/bin/bash
  • определение версии ОС ⇒ UNIX == Linux, если есть ОС-специфичные параметры, можно описать их в документации
  • поиск JRE/JDK ⇒ у нас единственная версия, и это OpenJDK (ну или Oracle JDK, если уж очень нужно), java и компания есть в стандартном системном пути
  • расчет числовых параметров JVM, тюнинг JVM ⇒ это можно описать в документации по скалированию приложения
  • поиск своих компонентов и библиотек ⇒ описать структуру приложения и способы ее настройки в документации
  • настройка окружения ⇒ описать в документации требования и особенности
  • генерация CLASSPATH-cp path/to/my/jars/* или даже, вообще, Uber-JAR
  • парсинг аргументов командной строки ⇒ аргументов не будет, т.к. обо всем, кроме запуска, позаботится менеджер процессов
  • сборка Java-команды
  • выполнение Java-команды

В итоге, нам нужно просто собрать и выполнить Java-команду вида java <opts> -jar <program. jar> с помощью выбранного менеджера процессов (Systemd, Docker и т.п.). Все параметры и опции (<opts>) мы оставляем на усмотрение системного инженера, который подстроит их под конкретную среду. Если список опций <opts> довольно длинный, можно вновь вернуться к идее startup-скрипта, но, в этом случае, максимально компактного и декларативного, т.е. не содержащего никакой программной логики.


В качестве примера давайте посмотрим, как можно упростить скрипт запуска Nexus 3.

Самый простой вариант, чтобы не залезать в дебри этого скрипта — просто запустить его в реальных условиях (./nexus start) и посмотреть на результат. Например, можно найти полный список аргументов запущенного приложения в таблице процессов (через ps -ef), или запустить скрипт в режиме отладки (bash -x ./nexus start), чтобы наблюдать весь процесс его выполнения и в самом конце — команду запуска.


У меня в итоге получилась следующая Java-команда

/usr/java/jdk1. 8.0_171-amd64/bin/java -server -Dinstall4j.jvmDir=/usr/java/jdk1.8.0_171-amd64 -Dexe4j.moduleName=/home/nexus/nexus-3.12.1-01/bin/nexus -XX:+UnlockDiagnosticVMOptions -Dinstall4j.launcherId=245 -Dinstall4j.swt=false -Di4jv=0 -Di4jv=0 -Di4jv=0 -Di4jv=0 -Di4jv=0 -Xms1200M -Xmx1200M -XX:MaxDirectMemorySize=2G -XX:+UnlockDiagnosticVMOptions -XX:+UnsyncloadClass -XX:+LogVMOutput -XX:LogFile=../sonatype-work/nexus3/log/jvm.log -XX:-OmitStackTraceInFastThrow -Djava.net.preferIPv4Stack=true -Dkaraf.home=. -Dkaraf.base=. -Dkaraf.etc=etc/karaf -Djava.util.logging.config.file=etc/karaf/java.util.logging.properties -Dkaraf.data=../sonatype-work/nexus3 -Djava.io.tmpdir=../sonatype-work/nexus3/tmp -Dkaraf.startLocalConsole=false -Di4j.vpt=true -classpath /home/nexus/nexus-3.12.1-01/.install4j/i4jruntime.jar:/home/nexus/nexus-3.12.1-01/lib/boot/nexus-main.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.apache.karaf.main-4.0.9.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.osgi.core-6.0.0.jar:/home/nexus/nexus-3. 12.1-01/lib/boot/org.apache.karaf.diagnostic.boot-4.0.9.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.apache.karaf.jaas.boot-4.0.9.jar com.install4j.runtime.launcher.UnixLauncher start 9d17dc87 '' '' org.sonatype.nexus.karaf.NexusMain

Вначале применим к ней пару простых приемов:


  • поменяем /the/long/and/winding/road/to/my/java на java, ведь она есть в системном пути
  • поместим список Java-параметров в отдельный массив, отсортируем его и уберем дубликаты

Получаем уже что-то более удобоваримое

JAVA_OPTS = ( '-server' '-Dexe4j.moduleName=/home/nexus/nexus-3.12.1-01/bin/nexus' '-Di4j.vpt=true' '-Di4jv=0' '-Dinstall4j.jvmDir=/usr/java/jdk1.8.0_171-amd64' '-Dinstall4j.launcherId=245' '-Dinstall4j.swt=false' '-Djava.io.tmpdir=../sonatype-work/nexus3/tmp' '-Djava.net.preferIPv4Stack=true' '-Djava.util.logging.config.file=etc/karaf/java. util.logging.properties' '-Dkaraf.base=.' '-Dkaraf.data=../sonatype-work/nexus3' '-Dkaraf.etc=etc/karaf' '-Dkaraf.home=.' '-Dkaraf.startLocalConsole=false' '-XX:+LogVMOutput' '-XX:+UnlockDiagnosticVMOptions' '-XX:+UnlockDiagnosticVMOptions' '-XX:+UnsyncloadClass' '-XX:-OmitStackTraceInFastThrow' '-XX:LogFile=../sonatype-work/nexus3/log/jvm.log' '-XX:MaxDirectMemorySize=2G' '-Xms1200M' '-Xmx1200M' '-classpath /home/nexus/nexus-3.12.1-01/.install4j/i4jruntime.jar:/home/nexus/nexus-3.12.1-01/lib/boot/nexus-main.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.apache.karaf.main-4.0.9.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.osgi.core-6.0.0.jar:/home/nexus/nexus-3.12.1-01/lib/boot/org.apache.karaf.diagnostic.boot-4.0.9.jar:/home/nexus/nexus-3.12.1-01/lib/boot/' ) java ${JAVA_OPTS[*]} com.install4j.runtime.launcher.UnixLauncher start 9d17dc87 '' '' org.sonatype.nexus.karaf.NexusMain

Теперь можно идти в глубину.

Install4j — это такой графический Java-инсталлятор. Похоже, что он используется для начальной установки системы. На сервере он нам не нужен, убираем.

Договоримся о размещении компоненты и данные Nexus на файловой системе:


  • поместим само приложение в /opt/nexus-<version>
  • для удобства создадим символическую ссылку /opt/nexus -> /opt/nexus-<version>
  • сам скрипт разместим вместо исходного как /opt/nexus/bin/nexus
  • все данные нашего Nexus будут лежать на отдельной файловой системе, смонтированной как /data/nexus

Само создание каталогов и ссылок — удел систем управления конфигурациями (на все про все 5-10 строчек в Ansible), поэтому оставим эту задачу системным инженерам.

Пусть наш скрипт при запуске меняет рабочий каталог на /opt/nexus — тогда мы сможем поменять пути к компонентам Nexus на относительные.

Опции вида -Dkaraf.* — это настройки Apache Karaf, OSGi-контейнера, в который, очевидно, "запакован" наш Nexus. Поменяем karaf.home, karaf.base, karaf.etc и karaf.data соответственно размещению компонентов, по возможности используя относительные пути.

Видя, что CLASSPATH состоит из списка jar-файлов, которые лежат в одном каталоге lib/, заменим весь этот список на lib/* (придется также выключить wildcard expansion с помощью set -o noglob).

Поменяем java на exec java, чтобы наш скрипт на запускал java как дочерний процесс (менеджер процессов этот дочерний процесс просто не увидит), а "заменял" себя на java (описание exec).

Посмотрим, что нас получилось:

#!/bin/bash JAVA_OPTS=( '-Xms1200M' '-Xmx1200M' '-XX:+UnlockDiagnosticVMOptions' '-XX:+LogVMOutput' '-XX:+UnsyncloadClass' '-XX:LogFile=/data/nexus/log/jvm.log' '-XX:MaxDirectMemorySize=2G' '-XX:-OmitStackTraceInFastThrow' '-Djava.io.tmpdir=/data/nexus/tmp' '-Djava. net.preferIPv4Stack=true' '-Djava.util.logging.config.file=etc/karaf/java.util.logging.properties' '-Dkaraf.home=.' '-Dkaraf.base=.' '-Dkaraf.etc=etc/karaf' '-Dkaraf.data=/data/nexus/data' '-Dkaraf.startLocalConsole=false' '-server' '-cp lib/boot/*' ) set -o noglob cd /opt/nexus \ && exec java ${JAVA_OPTS[*]} org.sonatype.nexus.karaf.NexusMain

Итого всего 27 строчек вместо >400, прозрачно, понятно, декларативно, никакой лишней логики. При необходимости этот скрипт легко превратить в темплейт для Ansible/Puppet/Chef и добавить туда только ту логику, которая нужна для конкретной ситуации.

Этот скрипт можно использовать в качестве ENTRYPOINT в Dockerfile или вызывать в unit-файле Systemd, заодно подстроив там ulimits и другие системные параметры, например:

[Unit] Description=Nexus After=network.target [Service] Type=simple LimitNOFILE=1048576 ExecStart=/opt/nexus/bin/nexus User=nexus Restart=on-abort [Install] WantedBy=multi-user. target

Какие выводы можно сделать из этой статьи? В принципе, все сводится к паре пунктов:


  1. У каждой системы свое предназначение, т.е., не нужно забивать гвозди микроскопом.
  2. Простота (KISS, YAGNI) рулит — реализовывать только то, что нужно для данной конкретной ситуации.
  3. И самое главное: круто, что есть IT-специалисты разного профиля. Давайте будем взаимодействовать и делать наши IT-системы проще, понятнее и лучше! :)

Спасибо за внимание! Буду рад обратной связи и конструктивной дискуссии в комментариях.

Java и Linux — особенности эксплуатации / Хабр

Java — очень распространённая платформа, на ней пишут очень разные вещи, начиная от Big Data, заканчивая микросервисами, монолитами, enterprise и прочим. И, как правило, всё это развёртывают на Linux серверах. При этом, соответственно, те люди, которые пишут на Java, зачастую делают это совсем на других операционных системах. Там они:

  • пишут код;
  • отлаживают, тестируют;
  • после этого упаковывают в jar;
  • отправляют на Linux, и оно работает.

В том, что оно работает, нет особой магии. Но это приводит к тому, что такие разработчики немножечко «засахариваются» в своём мире кроссплатформенности и не очень хотят разбираться, а как оно на самом деле работает в реальной операционной системе.

С другой стороны, есть те, кто занимается администрированием серверов, на их сервера устанавливают JVM, отправляют jar и war-файлы, а с точки зрения мира Linux все это:

  • чужеродное;
  • проприетарное;
  • собирается не из исходников;
  • поставляется какими-то jar-архивами;
  • «отъедает» всю память на сервере;
  • вообще, ведёт себя не по-человечески.

Цель доклада Алексея Рагозина на Highload++, расшифровка которого идет далее, была в том, чтобы рассказать особенности Java для «линуксоидов» и, соответственно, Linux — Java-разработчикам.

Доклад не будет разбором полётов, потому что проблем много, они все интересные, и снаряд дважды в одну воронку не попадает. Поэтому затыкать уже известные «дыры» — пораженческая позиция. Вместо этого поговорим про:

  • особенности реализации JVM;
  • особенности реализации Linux:
  • как они могут не стыковаться.

В Java есть виртуальная машина, и Linux, как и любая другая современная операционная система, по сути, — это тоже виртуальная машина. И в Java и в Linux есть управление памятью, потоки, API.

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


Память в Java


Сразу замечу, что я буду говорить только про реализацию JVM HotSpot, это Open JDK и Oracle JDK. То есть, наверняка в IBM J9 есть какие-то свои особенности, но я, к сожалению, про них не знаю. Если же мы говорим про HotSpot JVM, то картина мира выглядит следующим образом. Прежде всего в Java есть область, где живут java-объекты — так называемый Heap или, по-русски, куча, где работает сборщик мусора. Эта область памяти, как правило, занимает большую часть пространства процесса. Куча в свою очередь разбита на молодое и старое пространство (Young Gen / Old Gen). Не вдаваясь в дербри сайзинга JVM, важно то, что у JVM есть параметр «-Xmх», определяющий максимальный размер, до которого может вырасти пространство кучи.

А дальше есть много вариантов:

  • можно управлять отдельно размерами молодого пространства;
  • можно сразу выставить максимальный размер кучи;
  • либо дать возможность ему расти постепенно.

Деталей слишком много — важно, что есть лимит. И, в принципе, это общий подход ко всем областям, которые используют JVM. То есть практически все области, перечисленные на картинке выше, имеют определённый лимит. JVM сразу резервируетадресное пространство, исходя из лимита, а потом по мере необходимости запрашивает реальные ресурсы памяти в этом диапазоне. Это важно понимать.

Помимо кучи есть другие потребители памяти. Наиболее важными из них являются области памяти для стеков потоков. Потоки в Java — это обычные linux-потоки, у них есть стек, для которого резервируется определённый размер памяти. Чем больше у вас потоков, тем больше стеков выделено в памяти процесса. Поскольку число потоков в Java может измеряться сотнями и тысячими, иногда эта цифра может становиться достаточно существенной, особенно, если у вас какой-нибудь stateless microservice, в котором куча на 200 Мб, а ThreadPool на 50-100 потоков.

Кроме этого, есть ещё так называемые NIO Direct Buffers — это специальные объекты в Java, которые позволяют работать с памятью вне кучи. Они, как правило, используются для работы c I/O, потому что это память, к которой может напрямую обращаться как Си так и Java-код. Соответственно, эта область доступна через API, и у неё тоже есть максимальный лимит.

Остальное — это метаданные, какой-то сгенерированный код, память для них обычно не вырастает до больших величин, но она есть.

Помимо этих специальных областей не надо забывать, что JVM написана на C++, соответственно, там есть

  • malloc и обычная аллокация памяти;
  • библиотеки, которые подгружаются в JVM (статически или динамически слинкованные, которые тоже могут использовать память).

И эта память не классифицируется по нашей схеме, а просто является памятью, выделенной стандартными средствами C Runtime. C ней тоже иногда бывают проблемы, причём на достаточно ровном месте.

Например, вот у нас java код распространяется в jar виде. Jar — это zip-архив, для работы с ним используется библиотека zlib. Для того, чтобы что-то разархивировать zlib надо аллоцировать буфер, который будет использован для декомпрессии и, конечно, для него требуется память. Всё бы ничего, но сейчас есть мода на так называемые uber-jar, когда создается один здоровенный jar, и возникают нюансы.

При попытке старта из такого jar-файла открывается одновременно слишком много потоков zlib на распаковку. Причём с точки зрения Java всё хорошо: куча маленькая, все области маленькие, но потребление памяти процессом растёт. Это, конечно же «клинический» случай, но такие потребности JVM надо принимать ао внимание. Например, если вы установили -Xmx в 1 Гбайт, посадили Java в Docker-контейнер и поставили лимит памяти на контейнер тоже в 1 Гбайт, то JVM в него не поместится. Надо ещё чуть-чуть накинуть, а сколько точно — зависит от многих факторов, в том числе от количества потоков и от того, что именно ваш код делает.

Итак, это то, как JVM работает с памятью.

Теперь, так сказать, для другой части аудитории.

Память в Linux


В Linux нет никакого сборщика мусора. Его работа с точки зрения памяти совершенно другая. У него есть физическая память, которая разбита на страницы; есть процессы, у которых есть своё адресное пространство. Ему надо ресурсы этой памяти в виде страниц как-то разделить между процессами, чтобы они работали в своём виртуальном адресном пространстве и делали своё дело.

Размер страницы — обычно 4 килобайта. На самом деле это не так, в архитектуре x86 уже очень давно существует поддержка больших страниц. В Linux она появилась относительно недавно, и с ней пока немножко непонятная ситуация. Когда поддержка больших страниц (Transparent Huge Tables) появилась в Linux, очень многие люди наступили на грабли, связанные с performance degradation из-за некоторых нюансов обслуживания больших страниц в Linux. И вскоре интернет заполнился рекомендациями выключать их от греха подальше. После этого какие-то баги, связанные с работой больших страниц в Linux, починили, но осадок остался.

Но на текущий момент нет чёткого понимания, например, с какой версии поддержку больших страниц можно включить по умолчанию и не беспокоиться.

Поэтому будьте осторожны. Если вдруг на вашем Linux сервер внезапно на ровном месте вырастет потребление ресурсов ядром, то проблема может быть как раз в том, что у вас включены большие страницы, а сейчас они включены по умолчанию в большинстве дистрибутивов.

Итак, с точки зрения ядра у Linux есть множество страниц, которыми надо управлять. С точки зрения процесса — есть адресное пространство, в котором он резервирует диапазоны адресов. Зарезервированное адресное пространство — это ничто, не ресурс, в нём ничего нет. Если обратитесь по этому адресу, вы получите segfault, потому что там ничего нет.

Для того, чтобы в адресном пространстве появилась страница, нужен немножко другой syscall, и тогда процесс говорит операционной системе: «Мне, пожалуйста, в этих адресах нужен 1 Гбайт памяти». Но даже в этом случае память там появляется тоже не сразу и со своими хитростями.

С точки зрения ядра классификация страниц выглядит следующим образом, есть страницы:

a) приватные, то есть это значит, что они принадлежат одному процессу и доступны в адресном пространстве только одного процесса;

b) анонимные — это обычная память, не связанная в файлами;

c) memory mapped files — отображение файлов в память;

d) используемые совместно, которые могут быть либо:

  • Copy-On-Write, то есть, при ветвлении процесса, память становится доступна обоим процессам до тех пор, пока не будет записана и страницы не превратятся в приватные;
  • через Shared файл, т. е., если несколько процессов отображают в память один и тот же файл, то страницы могут использоваться совместно.

В общем, с точки зрения ядра операционной системы всё достаточно просто.

Просто, да не совсем.

Когда мы хотим понять, что происходит на сервере с точки зрения памяти, мы идём в top и видим там какие-то цифры. В частности, там есть использованная память и свободная память. Есть разные мнения насчет того, сколько должно быть свободной памяти на сервере. Кто-то думает, что это 5%, но, на самом деле, и 0% от физической памяти — также норма, потому что то, что мы видим в качестве счётчика свободной памяти, — это в действительности не вся свободная память. Её на самом деле намного больше, просто она, как правило, скрыта в кэше страниц.

С точки зрения процесса top показывает три интересные колонки:

  1. virtual memory;
  2. resident memory;
  3. shared memory.

Последняя память в списке — просто те страницы, которые являются совместно используемыми. А вот с resident memory всё немножко хитрее. Остановимся более подробно на этих метриках.

Как я уже сказал, используемая и свободная память, достаточно бесполезные метрики. У сервера ещё остается память, которая никогда не была использована, потому что у операционной системы есть файловый кэш, и все современные ОС всю свободную память используют под него, так как из файлового кэша страницу памяти всегда можно очистить и использовать для более важных задач. Поэтому вся свободная память постепенно уходит в кэш и не возвращается обратно.

Метрика виртуальной памяти —это вообще не ресурс с точки зрения операционной системы. Вы запросто можете аллоцировать 100 терабайт адресного пространства, и всё. Сделали это и можете гордиться, что с адреса X по адрес Y пространство зарезервировано, но не более. Поэтому смотреть на неё как на ресурс и, например, ставить алерт, что виртуальный размер процесса превысил какой-то порог, довольно бессмысленно.

Вернёмся к Java, она все свои специальные области резервирует заранее, потому что код JVM ожидает, что эти области будут непрерывными с точки зрения адресного пространства. А значит адреса надо застолбить заранее. В связи с этим, запуская процесс с кучей на 256 Мбайт, вы можете внезапно увидеть, что виртуальный размер у него больше двух гигабайт. Не потому, что эти гигабайты нужны и что JVM способна их когда-либо утилизировать, а просто дефолт такой. От него ни холодно, ни жарко, по крайне мере, так думали те, кто писал JVM. Это, правда, не всегда соответствует мнению тех, кто потом занимается поддержкой серверов.

Residence size — наиболее близкая к реальности метрика — это количество страниц памяти, которые используются процессом, находящимся в памяти, не в свопе. Но она тоже немножечко своеобразная.

Кэш


Возвращаюсь к кэшу. Как я уже сказал, кэш — это, в принципе, свободная память, но иногда бывают исключения. Потому что страницы в кэше бывают чистые и грязные (содержащие не сохранённые изменения). Если страница в кэше модифицирована, то прежде, чем она может быть использована для другой цели, её сначала надо записать на диск. А это уже совсем другая история. Пример, JVM пишет большой-большой Heap Dump. Делает она это неспешно, процесс происходи так:

  • JVM быстренько пишет в память;
  • операционная система выдаёт ей всю свободную память, которая у неё есть, под write behind cache, вся эта память оказывается «грязной»;
  • идет медленная запись на диск.

Если размер этого дампа сопоставим с размером физической памяти сервера, может возникнуть ситуация, что для всех остальных процессов свободной памяти просто не будет.

То есть у нас, например, открывается новая ssh-сессия — чтобы запустить shell-процесс, надо выделить память. Процесс идёт за памятью, а ядро говорит ему: «Подожди, пожалуйста, сейчас я что-нибудь найду». Находит, но прежде, чем оно успевает отдать эту страницу SSHD, Java успевает «запачкать» ещё несколько страниц, потому что она тоже «висит» в Page Fault и, как только появляется свободная страница, она быстренько успевает выхватить эту память раньше, чем какие-то другие процессы. На практике такая ситуация приводила, например, к тому, что система мониторинга просто решала, что этот сервер «не живой» раз зайти на него через ssh не получается. Но это, конечно, крайний случай.

Еще процесс в Linux помимо virtual size и resident size имеет committedsize — это та память, которую процесс реально собирается использовать, то есть это адресное пространство, при обращении к которому вы не получите segfault, при обращении к которому ядро обязано предоставить вам физическую страницу памяти.

В идеальной ситуации committed и re sident должны были быть одним и тем же. Но, во-первых, страницы могут «свопиться».

Во-вторых, память в Linux выделяется всегда лениво.

  • Вы говорите ему: «Дай мне, пожалуйста, 10 Гбайт». Он говорит: «Бери пожалуйста».
  • Другой процесс: «Дай мне тоже 10 Гбайт» — «Бери пожалуйста».
  • Третий процесс: «Дай мне тоже 10 Гбайт» — «Бери пожалуйста».

Потом оказывается, что физической памяти всего 16, а он всем раздал по 10. И начинается «кто первый взял, того и тапочки, а кому не повезло, за тем придёт OMKiller». Это особенности управления памятью Linux.

Важные факты о 

JVM

Первое, JVM очень не любит swapping. Если мне жалуются, что java-приложение почему-то тормозит, то первое, что я делаю, это смотрю, нет ли на сервере своппинга. Потому что есть два фактора, делающих Java очень нетолерантной к свопингу:

  1. Сборка мусора в Java постоянно бегает по страничкам, и если она «промахивается» мимо резидентных страниц, то вызывает перекладывание страниц с диска в память и обратно.
  2. Если в JVM хотя бы один поток «наступил» на страницу, которой в памяти нет, то это может привести к заморозке всех потоков этой JVM.

Есть механизм safe-point, который используется в JVM для всякой чёрной магии вроде перекомпиляции кода на лету, сборки мусора и так далее. Если один поток попал на Page Fault и ждёт, то JVM не может нормально войти в состояние safe-point, потому что не получает подтверждение от потока, который ждёт «приезда» страницы памяти. А все остальные потоки уже остановились и тоже ждут. Все стоят, ждут один этот несчастный поток. Поэтому, как только у вас начинается пейджинг, может начаться очень существенная деградация производительности.

Второе, Java никогда не отдаёт память операционной системе. Она будет использовать столько, сколько вы разрешили, даже если ей сейчас не очень нужны эти ресурсы, она их обратно не отдаст. Есть сборщики мусора, которые технически умеют это делать, но не надо рассчитывать, что они будут это делать.

У сборщика мусора такая логика работы: он либо использует больше
CPU, либо больше памяти. Если вы ему разрешили использовать 10 Гбайт, значит он разумно предполагает, что можно экономить ресурсы CPU, а эти 10 гигабайт с мусором подождут, а CPU пока пусть лучше делает делает что-то полезное, вместо чистки памяти, которая ещё не выходит за лимит.

В связи с этим важно правильно и обоснованно выставлять размер JVM. А если у вас несколько процессов в рамках одного контейнера, разумно распределять ресурсы памяти между ними.

Иначе пострадают все, что находится в этом контейнере.

Когда заканчивается память


Это еще одна из тех ситуаций, которыеочень по-разному воспринимаются «джавистами» и «линуксоидами».

В Java это происходит так: есть оператор new, который выделяет объект (на слайде это большой массив), если в куче недостаточно места, чтобы выделить память под этот большой массив, мы получаем Out of Memory error.

В Linux всё по-другому. Как мы помним, Linux легко может пообещать больше памяти, чем есть на самом деле, и вы начинаете с ней работать (выше условный код). И в отличии от JVM вы получите не ошибку, а аварийное завершение процесса выбранного OMKiller или смерть всего конейнера, если речь идёт о превышении квоты cgroups.

Когда память заканчивается в JVM


Теперь, разберемся чуть подробнее. В Java у нас есть так называемая область молодых объектов и область старых. Когда мы вызываем оператор new, объект выделяется в пространстве молодых объектов. Если же место в пространстве молодых объектов закончилось, происходит либо молодая, либо полная сборка мусора, если молодой сборки недостаточно. Суть в том, что, во-первых, если у нас не хватает памяти, происходит сборка мусора. И прежде, чем произойдёт Out of Memory error пройдет по крайне мере одна полная сборка, т.е. такая неспешная через всю нашу десятигигабайтную кучу. В некоторых случаях она будет ещё и в один поток, потому что full GC — особый случай.

При этом сборщик мусора наверняка чего-нибудь да наскребёт. Но если этого чего-нибудь меньше, чем 5% от размера кучи, всё равно будет выброшена ошибка, потому что это уже «не жизнь, а сплошное мучение». Но если этот Out of Memory error произойдёт в потоке, автор кода которого решил, что его поток должен работать, не взирая на любые ошибки, он может эту агонию продлевать путём перехвата исключений.

Вообще, после того, как стрельнула Out of Memory error, JVM уже нельзя считать живой. Внутренне состояние уже может быть разрушено, и есть такая опция (-XX:OnOutOfMemoryError="kill -9 %p"), которая позволяет сразу убить этот процесс. Опять же есть нюансы. Если у вас размер JVM сопоставим с размером физической памяти бокса, то в момент вызова этой команды у вас произойдёт форк, который. приведёт к тому, что образ JVM будет продублирован. Соответственно, с точки зрения Linux память для JVM может немножечко превысить предел максимальной памяти, которую он готов выделить и эта команда не сработает. Такая проблема типична для Hadoop-серверов, например, когда большущий узел пытается запустить Python через shell. Естественно, этому дочернему процессу столько памяти не нужно, просто форк делает копию всего, а уже потом освобождает ненужную память. Только «потом» не всегда наступает.

Может быть другая ситуация, возможно, что куча ещё не максимального размера (меньше -Xmx), но сборка мусора не собрала достаточно памяти, и JVM решила, что надо увеличить кучу. Пошла к операционной системе, говорит: «дай мне больше памяти», а ОС говорит: «нет». Правда, как я уже сказал, Linux так не говорит, но другие системы говорят. Любая ошибка выделения памяти операционной системы с точки зрения JVM — это crash, без вопросов, никаких эксепшенов, никаких логов, только стандартный crash dump, и немедленное завершение процесса.

Есть ещё второй тип Out of Memory, который связан с так называемыми direct memory buffers. Это специальные объекты в Java, которые ссылаются на память вне кучи. Соответственно, они же её и аллоцируют, управляют жизненным циклом этой памяти, то есть определённая сборка мусора там всё равно есть. Чтобы такими буферами нельзя было занять бесконечно большое количество памяти, на них есть лимит, который JVM сама себе выставляет. Иногда возникает необходимость его подкорректировать, на что, естественно, есть магическая -XX опция, например, -XX:MaxDirectMemorySize=16g. В отличие от нормального Out of Memory этот Out of Memory — recoverable, потому что он возникает в определённом месте и его возможно отличить от другого типа ошибок.

Выделяем память в Java

Как я уже говорил, JVM на старте важно знать, сколько вы ей позволите использовать памяти, потому что исходя из этого строятся все эвристики сборщика мусора.

Сколько выделять памяти «в граммах» — это вопрос сложный, но вот основные тезисы:

  1. Вы должны понимать, сколько полезных объектов должны находиться в памяти постоянно (Live set). Это правильнее всего измерять эмпирически, то есть надо:
    • производить тесты;
    • делать Heap Dump;
    • смотреть, из чего состоит куча и как она будет расти при увеличении числа запросов или количества данных.

  2. Молодое поколение либо берётся по умолчанию в процентном отношении от кучи, либо выставляется динамически. Например, фишка G1 collector как раз в том, что он сам умеет правильно выбирать размер young space. Для остальных сборщиков мусора лучше его выставлять руками, опять же исходя из эмпирических соображений.
  3. Сборщику мусора обязательно нужен резерв, так как для того, чтобы собирать мусор, он где-то должен в памяти быть. Чем больше у вас памяти под мусор, тем меньше CPU будет тратиться на 1 Гбайт освобождённой памяти. Этот баланс никогда нельзя «выкрутить в ноль». Размер резерва зависит от особенностей вашего приложения и используемого сборщика мусора, как правило, это 30–50%.
  4. Итого, общий размер вашей кучи (-Xmx) состоит из:
    • размерамолодого поколения;
    • размера live set;
    • резерва.

Помимо кучи есть ещё direct buffers, какой-то резервJVM, который тоже надо определять эмпирически.

Таким образом footprint процесса в целом всегда будет больше, чем -Xmx, причем это не просто какой-то процент, а сочетание различных факторов вроде количества потоков.

Выделяем память в Linux

Двигаемся дальше, в Linux есть такая штука как ulimit — это такая странная конструкция, на мой взгляд джависта. Для процесса есть набор квот, который задает операционная система. Квоты есть разные, на количество открытых файлов, что логично, и ещё на какие-то другие вещи.

Именно для управления ресурсами ulimits работают не очень — для того, чтобы ограничивать ресурс контейнера, используется другой инструмент. В ulimits есть максимальный размер памяти, который на Linux не работает, но там же есть ещё максимальный размер виртуальной памяти. Это такая интересная штука, потому что, как я уже говорил, виртуальная память — не ресурс. В принципе, от того, что я зарезервирую 100 Тбайт адресного пространства, операционной системе ни холодно ни жарко. Но ОС скорее всего не даст мне этого сделать, пока я для своего процесса не становлю соответствующий ulimit.

По умолчанию этот лимит есть и может помешать запускаться вашей JVM, особенно опять же если размер JVM сопоставим с физической памятью, потому что значение по умолчанию часто считается как раз от размера физической памяти. Это вызывает некоторое недоумение, когда, допустим, у меня на сервере 500 Гбайт, я пытаюсь запустить JVM на 400 Гбайт, а она просто падает на старте с какими-то непонятными ошибками. Потом выясняется, что JVM на старте выделяет себе все эти адресные пространства, и в какой-то момент ОС ей говорит: «Нет, что-то много ты брешь адресного пространства, мне жалко». И, как я уже сказал, в этом случае JVM просто «умирает». Поэтому иногда этот параметрам нужно не забыть настроить.

Бывают другие клинические ситуации, когда люди почему-то решают, что если у них для JVM на сервере выделено 20 Гбайт, то надо ей размер виртуального адресного пространства тоже выставить в 20 Гбайт. Это проблема, потому что некоторые участки памяти, которые JVM резервирует, никогда не будут использованы, и их достаточно много. Таким образом вы намного сильнее ограничиваете ресурсы памяти этого процесса, чем можете подумать.

Поэтому обращаюсь к линуксоидам, пожалуйста, не делайте так, пожалейте своих джавистов.

Пару слов про Docker

То есть не про сам Docker, а про управление ресурсами в контейнере. В Docker управление ресурсами для контейнеров работает через механизм cgroups. Это механизм ядра, который позволяет для дерева процессов ограничить всевозможные ресурсы, например CPU и память. В том числе для памяти можно ограничить размер резидентной памяти, занимаемой всем контейнером, количество swap, количество страниц и др. Эти лимиты, в отличие от ulimits, нормальные ограничения на весь контейнер; если процесс форкает какие-то дочерние процессы, то они попадают в ту же группу ограничения ресурсов.

Что важно:

  1. Если вы запускаете Java в docker-контейнер, она смотрит, сколько на хосте физической памяти, и исходя из того, сколько физической памяти реально на хосте, а не в контейнере считает ограничения по умолчанию. И очень быстро умирает, потому что ей столько не дают. Поэтому -Xmx обязательно — без этого не взлетит.
  2. Всегда под контейнер надо давать немножко больше памяти, чем под JVM. Допустим, вы делаете контейнер на 2 Гбайта, запускаете JVM с параметром -Xmx2048m, оно как-то начинает работать, потому что память аллоцируется лениво. Но потихонечку все эти страницы так или иначе начинают использоваться, и сначала ваш контейнер начинает уходить в локальный своп, а потом просто умирает. Причём умирает он в лучших традициях — просто исчезает.

Если оно просто стартовало, это ещё ничего не значит, потому что ресурсы выделяются реально лениво.

Потоки в Java

Про потоки в Java важно знать, что они — нормальные потоки операционной системы. Когда-то в первых JVM были реализованы так называемые green threads — зелёные потоки, когда на самом деле стек java-потока как-то жил своей жизнью, и один поток операционной системы выполнял то один java-поток, то другой. Это всё развивалось до тех пор, пока в операционных системах не появилась нормальная многопоточность. После этого все забыли «зелёные» потоки как страшный сон, потому что с нативными потоками код работает лучше.

Это значит, что stack trace на пол тысячи фреймов реально лежит в том пространстве стека, который выделила операционная система. Если вы вызываете какой-то нативный код из Java это код будет использовать тоже самый стек, что и java-код. Это означает возможность использования диагностического инструментария, который есть в Linux, для работы так же и с java-потоками.

Как найти java

-потоки

Если мы воспользуемся командой ps для JVM, мы увидим такую непонятную картину, потому что все потоки называются одинаково. Но на практике там в порядке очереди идут:

  • потоки сборщика мусора;
  • так называемый operational thread JVM;
  • application потоки,

но это наугад.

На самом деле? если вы снимете thread dump с JVM с помощью командыjstack, то там будет шестнадцатеричное число «TID» — это идентификатор реального линуксового потока. То есть вы можете понять, какие java-потоки соответствуют каким потокам операционной системы и расшифровать ps.

Единственное, если вы уже видите, как напишете скрипт на perl, который будет это делать, не вызывайте jstack в цикле, лучше наоборот. Потому что каждый раз, когда вы вызываете jstack, вы вызываете глобальную паузу всех потоков JVM. В нормальных обстоятельствах это быстро, меньше, чем полмиллисекунды, но если делать такое 20 раз в секунду, то это уже может заметно отразится на производительности.

Можно, так же, вытащить эту информацию из самой JVM, в которой есть свой диагностический интерфейс. В частности, можете воспользоваться моим инструментом, который оттуда эту информацию вытягивает и просто для JVM печатает топ по потокам. Помимо CPU usage он ещё умеет печатать интенсивность аллокаций памяти кучи Java потоками.

Итого о потоках



Java-потоки — это обычные потоки операционной системы. В современных версиях JVM есть ключ PreserveFramePointer — это опция JIT-компилятора, позволяющая инструментам типа perf корректно парсить стек Java потоков.

Также есть на GitHub проект, который позволяет экспортировать «на лету» символы для скомпилированного java-кода, и с помощью того же perf получать вполне читабельный стек вызовов.

И небольшое напоминание, что у нас есть ещё потоки сборщика мусора.

Если у вас контейнер, на котором вы выделили два CPU, то надо количество параллельных потоков сборщика мусора тоже сделать два, потому что по умолчанию их будет больше, и они будут друг другу только мешать.

С другой стороны, в тот момент, когда работает сборщик мусора, все остальные потоки не делают ничего. Поэтому под сборщика мусора можно выделять 100% ресурсов контейнера, которые вы собираетесь выделить для Java.

IO и Networking


Сетевому стеку в Linux нужен тюнинг. Про это очень хорошо помнят те, кто занимается фронтенд-серверами, например, с Nginx, но то же самое неплохо бы делать на application и бэкенд-серверах — про это иногда забывают. И всё работает нормально до тех пор, пока у вас система становится геораспределенной и не начинается перегон данных через Атлантику. И упс, оказывается, надо было увеличить лимит на буферы.

Если вы используете UDP коммуникации, это тоже требует отдельной настройки на уровне операционной системы. Есть опции, которые код сам должен выставлять через API на сокетах, но они должны быть разрешены на уровне лимитов операционной системы. Иначе они просто не работают.

Второй интересный момент связан с особенностями работы с ресурсами в JVM.

У нас есть ограниченный ресурс — лимит на количество файлов, куда попадают сокеты и т.д., для процесса. Если у нас этот лимит превышен, то мы не можем:

  • открывать соединение;
  • открывать файлы;
  • принимать соединения и т.д.

В Java на всех этих объектах есть методы для того, чтобы их явным образом закрывать и, соответственно, освобождать дескрипторы Linux.

Но если ленивый джавист этого не сделал, то сборщик мусора придёт и всё равно за него всё закроет. И всё бы хорошо, если бы этот сборщик мусора приходил по расписанию, но он приходит, когда посчитает нужным. Если у вас вся куча забита незакрытыми сокетами, то с точки зрения кучи это копейки, потому что там лежат только метаданные этого сокета и номер дескриптора из операционной системы. Поэтому если у вас есть вот такая комбинация внешних ресурсов, на которые ссылается java-код, то сборщик мусора иногда может вести себя не очень адекватно в этом плане.

Соединения и файлы всегда надо закрывать руками.

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

Есть пара ресурсов в JVM, которые нельзя освободить явным образом:

  • memory map файлы;
  • NIO direct buffers.

Поэтому с ними надо работать аккуратно, и желательно не выбрасывать, а переиспользовать. С точки зрения диагностики у нас есть heap dump, из которого всю эту информацию можно вытащить.

И, наконец, последние напутствия.

Выставляйте правильный размер JVM. Сама JVM не знает, сколько памяти ей нужно взять.

Учитесь пользоваться инструментами, в Linux есть инструменты, которые достаточно неплохо работают с Java, в JDK есть инструменты, которые позволяют получать много информации именно через командную строку. В Java есть JMX (Java Management Extensions) диагностический интерфейс, но для того, чтобы с ним работать, нужен другой java-процесс, что не всегда удобно.

В частности, не забывайте, про сочетание инструментов. Например, если у вас есть Linux core dump JVM, то вы можете с помощью инструментов JDK вытащить из него heap dump для Java и посмотреть его уже нормальным джавовским анализатором вместо того, чтобы делать этот heap dump непосредственно с живого процесса.

И уже совсем напоследок несколько ссылок на разные темы.

Java Memory Tuning and Diagnostic:

  • http://blog.ragozin.info/2016/10/hotspot-jvm-garbage-collection-options.html
  • https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html
  • Using JDK tools with Linux core dumps
    https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/bugreports004.html#CHDHDCJD

Linux Transparent Huge Pages reading:

  • https://www.perforce.com/blog/tales-field-taming-transparent-huge-pages-linux
  • https://tobert.github.io/tldr/cassandra-java-huge-pages.html
  • https://alexandrnikitin.github.io/blog/transparent-hugepages-measuring-the-performance-impact/

Profiling and performance monitoring:

  • https://github.com/jvm-profiling-tools/perf-map-agent
  • https://github.com/aragozin/jvm-tools

Контакты:

  • https://blog. ragozin.info
  • https://github.com/aragozin

Если у вас остались вопросы, то можно перескочить на соответствующую часть
доклада, может быть, кто-то это уже уточнил.

Короткое послесловие

РИТ++ уже 28 и 29 мая, расписание здесь, а это прямая ссылка на покупку билетов.

До Highload++ Siberia времени чуть больше, она пройдет 25 и 26 июня. Но программа уже активно формируется, вы можете подписаться на рассылку и быть в курсе обновлений.

Hello World на Java в Linux


Эта среда программирования Java на основе DrJava больше не поддерживается. (поскольку DrJava больше не разрабатывается активно и DrJava несовместима с Java 11). Он был заменен следующей средой программирования на основе IntelliJ для Линукс.

В этом документе рассказывается, как настроить среду программирования Java. под линукс. Он также обеспечивает пошаговое руководство по созданию, компиляции и выполнению вашего первого Java программу с помощью DrJava или командной строки. Мы предполагаем некоторое знакомство с командной строкой. Все программное обеспечение находится в свободном доступе в Интернете.

0.   Установите Java


Вы будете использовать Java Platform, Standard Edition Development Kit (JDK 8).

1.   Установите среду программирования


Эти шаги устанавливают и настраивают библиотеки наших учебников, DrJava, Checkstyle и Findbugs. Если вам не нужны DrJava, Checkstyle или Findbugs, просто пропустите соответствующий шаг.

2.   Создайте программу в DrJava


Теперь вы готовы написать свою первую программу на Java. Вы будете разрабатывать свои Java-программы в приложении под названием DrJava . DrJava имеет множество специализированных инструментов программирования, включая подсветку синтаксиса, сопоставление скобок, автоматический отступ и нумерация строк.

3.  Скомпилировать программу из DrJava


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

 Компиляция завершена. 

Если DrJava каким-то образом жалуется, вы что-то опечатались. Внимательно проверьте свою программу, используя сообщения об ошибках на панели вывода компилятора. в качестве гида.

4.  Выполнение программы из DrJava


Теперь пришло время запустить вашу программу. Это самое интересное.

5.  Скомпилируйте программу из оболочки

Вы будете использовать команду javac для преобразования вашей Java-программы в более поддается исполнению на компьютере.

6.  Выполнение программы из командной консоли


Вы будете использовать команду java для выполнения вашей программы.

7. Checkstyle и Findbugs


Вы можете использовать Checkstyle и Найтибаги для проверки стиля ваших программ и выявления распространенных ошибок.

Поиск и устранение неисправностей

Мой дистрибутив Linux { Gentoo, Debian, Ubuntu, Fedora, Red Hat, SuSE, Mandriva или Slackware}. Как мне изменить инструкции? Мы не проверяли эти инструкции на всех разновидностях Linux, но инструкции должны быть идентичными, за исключением установки Java. Мы рекомендуем использовать ваш дистрибутив менеджер пакетов (например, portage, apt, emerge или yum), чтобы установить Java. Вот несколько инструкций по установке OpenJDK.

Могу ли я использовать другую версию Java? Любая версия Java 8 (либо Oracle, либо OpenJDK) или более поздняя версия должна работать нормально.

Мне пришлось вручную ввести расположение tools.jar в DrJava, но это не кажется, есть какой-то эффект. Какие-либо предложения? Этот параметр не вступит в силу, пока вы не перезапустите DrJava.

Могу ли я использовать другую IDE? Да, вы можете использовать другую IDE (например, Eclipse), но вам придется самостоятельно настроить свойства IDE (например, путь к классам).

Как определить, какую оболочку я запускаю? Введите следующую команду:

 [имя пользователя:~/]  эхо $SHELL  бить 

Ваша оболочка, скорее всего, будет

bash

,

tcsh

,

sh

,

кш

или

зш

.

Как я могу проверить, что /usr/local/bin находится в моем PATH? В оболочке введите следующую команду:

 [имя пользователя:~/]  эхо $PATH  

Вы должны увидеть запись /usr/local/bin с записями, разделенными двоеточием (:).

Как добавить /usr/local/bin в PATH? Это существенно зависит от вашего дистрибутива Linux, оболочки и конфигурации пользователя. В оболочке bash это обычно означает добавление следующего строку в ваш файл .bash_profile, .profile или .bashrc. Для других распространенных оболочек это может означать обновление вашего Файлы .cshrc, .tcshrc или .zcshrc.

 # Добавляем ~/introcs/bin в ПУТЬ экспорт PATH=$PATH:$HOME/introcs/bin 

Возможно, вам придется выйти и снова войти в систему, чтобы изменения вступили в силу.

Когда я компилирую или запускаю программу из оболочки, которая использует один из библиотеки учебников, я получаю сообщение об ошибке. Как я могу это исправить? Во-первых, убедитесь, что вы используете javac-introcs и java-introcs. скрипты-обертки. Затем убедитесь, что у вас есть файл /usr/local/introcs/stdlib.jar.

Copyright © 2000–2019 и . Все права защищены.

командная строка — открыть терминал при запуске и запустить приложение Java

спросил

Изменено 7 лет, 9 месяцев назад

Просмотрено 4к раз

Вот я снова после успешной установки Ubuntu MATE 14. 04 с помощью AskUbuntu (большое спасибо!)

Сейчас я пытаюсь добиться следующего:

  1. Запустить терминал с привилегиями root при запуске
  2. Изменить рабочий каталог на Рабочий стол
  3. Выполнение файла Jar с помощью команды «java -jar имя_файла.jar»

Как проще всего это сделать, желательно без установки каких-либо сторонних программ?

РЕДАКТИРОВАТЬ: Я не только смотрю, как запустить скрипт при запуске, но и запускаю командную строку и выполняю команду.

  • командная строка
  • Java

2

Вы можете создать корневую оболочку bash внутри экземпляра mate-terminal , изменить рабочий каталог mate-terminal на ~/Desktop и выполнить файл jar внутри него при запуске, добавив эту команду для запуска приложений:

 mate-terminal -e "sudo -H /bin/bash -c \"cd ~/Desktop && java -jar executable. 

Learn more

Только новые статьи

Введите свой e-mail

Видео-курс

Blender для новичков

Ваше имя:Ваш E-Mail: