Я думаю, что решение для создания отдельного фонового процесса, на который JVM не ссылается, также поддерживает возможность передавать любые аргументы в здравом смысле, если использовать API CreateProcess
.Для этого я использовал:
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>platform</artifactId>
<version>3.5.0</version>
</dependency>
(Это более старая версия, но она уже используется).
Код Jave для работы:
/**
*
* @param command a pre escaped command line e.g. c:\perl.exe c:\my.pl arg
* @param env A non null environment.
* @param stdoutFile
* @param stderrFile
* @param workingDir
*/
public void execute(String command, Map<String, String> env,
File stdoutFile, File stderrFile, String workingDir) {
WinBase.SECURITY_ATTRIBUTES sa = new WinBase.SECURITY_ATTRIBUTES();
sa.bInheritHandle = true; // I think the child processes gets handles I make with
// with this sa.
sa.lpSecurityDescriptor = null; // Use default access token from current proc.
HANDLE stdout = makeFileHandle(sa, stdoutFile);
HANDLE stderr = null;
if(stderrFile != null &&
!stderrFile.getAbsolutePath().equals(stdoutFile.getAbsolutePath())) {
stderr = makeFileHandle(sa, stderrFile);
}
try {
WinBase.STARTUPINFO si = new WinBase.STARTUPINFO();
// Assume si.cb is set by the JVM.
si.dwFlags |= WinBase.STARTF_USESTDHANDLES;
si.hStdInput = null; // No stdin for the child.
si.hStdOutput = stdout;
si.hStdError = Optional.ofNullable(stderr).orElse(stdout);
DWORD dword = new DWORD();
dword.setValue(WinBase.CREATE_UNICODE_ENVIRONMENT | // Probably makes sense.
WinBase.CREATE_NO_WINDOW | // Well we don't want a window so this makes sense.
WinBase.DETACHED_PROCESS); // I think this would let the JVM die without stopping the child
// Newer versions of platform don't use a reference.
WinBase.PROCESS_INFORMATION.ByReference processInfoByRef = new WinBase.PROCESS_INFORMATION.ByReference();
boolean result = Kernel32.INSTANCE
.CreateProcess(null, // use next argument to get the task to run
command,
null, // Don't let the child inherit a handle to itself, because I saw someone else do it.
null, // Don't let the child inherit a handle to its own main thread, because I saw someone else do it.
true, // Must be true to pass any handle to the spawned thread including STDOUT and STDERR
dword,
asPointer(createEnvironmentBlock(env)), // I hope that the new processes copies this memory
workingDir,
si,
processInfoByRef);
// Put code in try block.
try {
// Did it start?
if(!result) {
throw new RuntimeException("Could not start command: " + command);
}
} finally {
// Apparently both parent and child need to close the handles.
Kernel32.INSTANCE.CloseHandle(processInfoByRef.hProcess);
Kernel32.INSTANCE.CloseHandle(processInfoByRef.hThread);
}
} finally {
// We need to close this
// /7593106/nuzhno-li-zakryvat-unasledovannyi-deskriptor-prinadlezhaschii-potom-dochernemu-protsessu
Kernel32.INSTANCE.CloseHandle(stdout);
if(stderr != null) {
Kernel32.INSTANCE.CloseHandle(stderr);
}
}
}
private HANDLE makeFileHandle(WinBase.SECURITY_ATTRIBUTES sa, File file) {
return Kernel32.INSTANCE
.CreateFile(file.getAbsolutePath(),
Kernel32.FILE_APPEND_DATA, // IDK I saw this in an example.
Kernel32.FILE_SHARE_WRITE | Kernel32.FILE_SHARE_READ,
sa,
Kernel32.OPEN_ALWAYS,
Kernel32.FILE_ATTRIBUTE_NORMAL,
null);
}
public static byte[] createEnvironmentBlock(Map<String, String> env) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// This charset seems to work.
Charset charset = StandardCharsets.UTF_16LE;
try {
for(Entry<String, String> entry : env.entrySet()) {
bos.write(entry.getKey().getBytes(charset));
bos.write("=".getBytes(charset));
bos.write(entry.getValue().getBytes(charset));
bos.write(0);
bos.write(0);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
bos.write(0);
bos.write(0);
return bos.toByteArray();
}
public static Pointer asPointer(byte[] data) {
Pointer pointer = new Memory(data.length);
pointer.write(0, data, 0, data.length);
return pointer;
}
- ✓ Кажется, что запущенный процесс продолжает работать, даже если JVM остановлена.
- ✓ Мне не нужно было иметь дело с STDOUT / STDERR с
ProcessBuilder
, а затем еще одним.от необходимости перенаправить команду на самом деле запустить. - ✓ Если вы можете правильно экранировать свою команду (код для выполнения которого отсутствует в ответе, поскольку он не мой для совместного использования), вы можете передать такие вещи, как
"
, которыйЯ не мог понять, как это сделать при использовании ProcessBuilder
с cmd /c start /b command
.Казалось, что JVM делает какое-то побег, делая невозможным создание необходимой строки для получения правильной команды. - ✓ Я мог видеть, что дескрипторы файлов, удерживаемые JVM для stdout / stderr, освобождаются до завершения процесса.
- ✓ Я мог бы создать 13 тыс. Задач без JVM, генерирующей OOM с 62 МБ памяти, выделенной JVM (похоже, что JVM не удерживает ресурсы, как некоторые люди в конечном итоге просто делают
ProcessBuilder
и cmd /c
. - ✓ промежуточное звено
cmd.exe
не создано