Перейти к содержимому

Java opts как правильно выставить параметры

  • автор:

Как правильно редактировать использование памяти Java

-Xmn определяет объём занятой памяти до которой сборщик мусора должен освобождать память (если это возможно);
-Xms определяет размер начальной выделенной памяти под объекты;
-Xmx определяет максимальный размер памяти, выше которого приложение не задействует;

Правило настроек запуска:
А также:

Xms примерно равно Xmx ;
Xmn примерно половина от Xms ;

Java «Heap» is a continous memory region where all Objects data will be stored (by data, we mean instance of class, primitive and references). It’s a big part of the process heap. It can be configured using the following parameters:

-Xmx : max heap size (ex: -Xmx1024);
-Xms : min heap size. Having -Xms = 1.8GB (32bit) can be bad, because you don’t let memory for anything else;
-Xmn : the size of the heap for the young generation.

Young generation represents all the objects which have a short life of time. Young generation objects are in a specific location into the heap, where the garbage collector will pass often. All new objects are created into the young generation region (called «eden»). When an object survive is still «alive» after more than 2-3 gc cleaning, then it will be swap has an «old generation» : they are «survivor» .
Good size is 33%

-XX:NewRatio : the same as Wmn, but using a % (dynamic fs static -Xmn option);
-XX:NewRatio=3 means that the ratio between the old and young generation is 1:3;
-XX:NewSize — Size of the young generation at JVM init. Calculated automatically if you specify -XX:NewRatio ;
-XX:MaxNewSize — The largest size the young generation can grow to (unlimited if this value is not specified at command line);
-XX:SurvivorRatio : «old generation» called tenured generation, ratio, in %. For example, -XX:SurvivorRatio=6 sets the ratio between each survivor space and eden to be 1:6 (eden is where new objects are created);
-XX:MinHeapFreeRatio: default is 40%. JVM will allocate memory to always have as minimum 40% of free memory. When -Xmx = -Xms, it’s useless;
-XX:MaxHeapFreeRatio: default is 70%. The same as Min, to avoid unecessary memory allocation.

Настройка JVM и HTTPS в Tomcat

Необходимо создать файл setenv в CATALINA_BASE/bin с нужным расширением, в зависимости от ОС: linux — .sh , windows — .bat .

Добавить в файл:

export JAVA_OPTS=»» для linux

set JAVA_OPTS= для windows

Добавить в JAVA_OPTS необходимые параметры:

  • для установки JVM в режим сервера:
  • для настройки памяти:

В общем виде это выглядит так:

для windows — setenv.bat:

set JAVA_OPTS=-server -Xmx2g -Xms2g -Dfile.encoding=UTF-8 -XX:MaxPermSize=768m

для linux — setenv.sh:

export JAVA_OPTS=»-server -Xmx2g -Xms2g -Dfile.encoding=UTF-8 -XX:MaxPermSize=768m»

Настройка HTTPS в Tomcat

Настройка HTTPS в Tomcat:

Создайте хранилище ключей при помощи библиотеки keytool, которая обычно входит в JDK:

Для этого перейдите в каталог %JAVA_HOME%/bin

keytool -genkey -alias tomcat -keyalg RSA

Введите пароль changeit

Заполните данными ssl хранилище

В пункте имя сайте нужно указать адрес сайта!

Например localhost или www.вашСайт.org

Введите пароль для tomcat: changeit

Подтвердите ввод данных нажатием клавиши Y затем Enter. Ключ должен сохраниться в документах и иметь имя .keystore

Скопируйте ключ в каталог %TOMCAT_HOME%/conf/

Откройте для редактирования файл %TOMCAT_HOME%/conf/sever.xml

В случае использования Native APR замените на

Как сделать простым и понятным запуск 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 , учитывая наличие удобных современных 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=»$:$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 -jar с помощью выбранного менеджера процессов (Systemd, Docker и т.п.). Все параметры и опции ( ) мы оставляем на усмотрение системного инженера, который подстроит их под конкретную среду. Если список опций довольно длинный, можно вновь вернуться к идее 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 $ com.install4j.runtime.launcher.UnixLauncher start 9d17dc87 '' '' org.sonatype.nexus.karaf.NexusMain

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

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

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

  • поместим само приложение в /opt/nexus-
  • для удобства создадим символическую ссылку /opt/nexus -> /opt/nexus-
  • сам скрипт разместим вместо исходного как /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 $ 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 Blog

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

Обзор параметров JVM

Если говорить о параметрах JVM, то есть три типа параметров, которые вы можете включить в JVM: стандартные, нестандартные и расширенные параметры. Если вы используете расширенную опцию, вы всегда используете опцию с -XX. Точно так же, если вы применяете нестандартную опцию, вы используете -X. Стандартные опции ничего не добавляют к опции.

Какие параметры JVM используются вашим приложением?

Если приложение работает в Linux, вы можете использовать

ps -ef | grep java

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

Если ваш аргумент слишком длинный, попробуйте использовать

ps -auxww | grep java

так как эта команда также покажет длинный список аргументов.

Имея список флагов JVM, вы можете получить представление о поведении любого Java-приложения, например Tomcat.

1. Размер кучи (heap) Java
-Xms - установить начальный размер кучи Java -Xmx - установить максимальный размер кучи Java -Xss - установить размер стека Java-потока (java thread stack)

-Xms — эта опция определяет начальный размер кучи для JVM, например, Xms2048m, что означает, что начальный размер кучи JVM составляет около 2 ГБ. Итак, когда JVM запускается, куча памяти будет очень большой. Это делается для предотвращения изменения размера во время запуска и увеличения времени запуска JVM.

-Xmx — этот параметр определяет максимальный размер кучи JVM, например, Xmx2048m, что означает, что максимальный размер кучи JVM будет составлять только 2 ГБ.

По сути, вы всегда будете иметь -Xms и -Xmx вместе.

2. Задание процента кучи

-XX:MaxHeapFreeRatio — устанавливает максимальный процент свободного пространства кучи после GC (Garbage Collecting, сборки мусора), чтобы избежать сжатия.

-XX:MinHeapFreeRatio — устанавливает минимальный процент свободного пространства кучи после GC, чтобы избежать расширения; для мониторинга использования кучи вы можете использовать JConsole.

3. Включить обмен данными класса

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

4. PermGen размер

Ранее параметры JVM определяли размер кучи памяти, но -XX:PermSize — для определения размера пространства PermGen, в котором сохраняются пул строк и метаданные класса. Этот параметр особенно эффективен для веб-сервера, такого как Tomcat, который часто загружает классы веб-приложения во время развертывания.

Кстати, стоит понимать, что пространство PermGen занято Metaspace в Java 8, и этот параметр неприменим, если вы работаете с JRE 8 JVM.

5. Распечатать GC (Garbage Collector)
-verbose:gc - регистрирует, запуски сборщика мусора и сколько времени они занимают. -XX:+PrintGCDetails - включает в себя данные из -verbose:gc, но также добавляет информацию о размере нового поколения и более точных временных параметрах. -XX:-PrintGCTimeStamps - печатать метки времени при сборке мусора.

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

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

6. Обработка ошибки «OutOfMemory»

Чтобы вызвать дамп кучи при нехватке памяти, вы можете использовать -XX:+HeapDumpOnOutOfMemoryError

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

Дамп кучи будет установлен в «текущем каталоге» JVM по умолчанию. Если вы хотите создать дамп кучи в определенном каталоге, запустите

-XX:HeapDumpPath=[путь к каталогу дампа кучи] -XX:+UseGCOverheadLimit -XX:OnOutOfMemoryError="; "

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

Если мы хотим перезапустить сервер сразу после возникновения нехватки памяти, мы можем установить этот параметр с той же целью:

XX:OnOutOfMemoryError="shutdown -r"
7. Trace загрузки классов и выгрузки

-XX:+TraceClassLoading и -XX:+TraceClassUnloading — это две опции JVM, которые мы используем для печати информации журналов всякий раз, когда классы загружаются в JVM или выгружаются из JVM. Эти флаги JVM полезны, если у вас есть какой-либо тип утечки памяти, связанный с загрузчиком классов и есть подозрение, что классы не выгружаются или не собирается мусор.

8. Java classpath (путь к классу)

Говоря о Java Classpath а затем -Xbootclasspath определяет записи classpath, которые мы хотим загрузить без проверки. JVM проверяет все классы, которые она загружает, чтобы убедиться, что она не пытается разыменовать объект с помощью int, выталкивает дополнительные записи из стека или выталкивает слишком много, и так далее.

Помещение class в bootclasspath также снижает стоимость, но его следует использовать только тогда, когда вы знаете, что классы проверялись много раз раньше. В JRuby это сократило время запуска вдвое и более для простого скрипта.

9. Профилирование

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

-Xprof -Xrunhprof
10. 64-битная среда

В среде ОС, в которой установлены 32- и 64-разрядные пакеты, JVM автоматически выбирает 32-разрядные пакеты среды по умолчанию.

Если мы хотим установить 64-битную среду вручную, мы можем сделать это с помощью параметра -d . OS bit может быть 32 или 64.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *