Я начал процесс со следующим кодом
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "path");
try {
Process p = pb.start();
}
catch (IOException ex) {}
Теперь мне нужно знать процесс pid, который я только что начал.
Я начал процесс со следующим кодом
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "path");
try {
Process p = pb.start();
}
catch (IOException ex) {}
Теперь мне нужно знать процесс pid, который я только что начал.
Пока нет публичного API. см. Sun Ошибка 4244896, Sun Ошибка 4250622
В качестве обходного пути:
Runtime.exec(...)
возвращает объект типа
java.lang.Process
Класс Process является абстрактным, и вы возвращаетесь к некоторому подклассу процесса, который предназначен для вашей операционной системы. Например, на Mac, он возвращает java.lang.UnixProcess
, у которого есть частное поле с именем pid
. Используя Reflection, вы можете легко получить значение этого поля. Это, по общему признанию, хак, но это может помочь. Для чего вам нужен pid
?
На этой странице есть HOWTO:
http://www.golesny.de/p/code/javagetpid
В Windows:
Runtime.exec(..)
Возвращает экземпляр "java.lang.Win32Process" ) ИЛИ "java.lang.ProcessImpl"
Оба имеют частный дескриптор поля.
Это дескриптор ОС для процесса. Вам придется использовать этот + Win32 API для запроса PID. На этой странице есть сведения о том, как это сделать.
Поскольку класс Java 9 Process
имеет новый метод long pid()
, поэтому он так же прост, как
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "path");
try {
Process p = pb.start();
long pid = p.pid();
} catch (IOException ex) {
// ...
}
В Unix-системе (Linux и Mac)
public static synchronized long getPidOfProcess(Process p) {
long pid = -1;
try {
if (p.getClass().getName().equals("java.lang.UNIXProcess")) {
Field f = p.getClass().getDeclaredField("pid");
f.setAccessible(true);
pid = f.getLong(p);
f.setAccessible(false);
}
} catch (Exception e) {
pid = -1;
}
return pid;
}
Включите в свою библиотеку jna ("JNA" и "JNA Platform") и используйте эту функцию:
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinNT;
import java.lang.reflect.Field;
public static long getProcessID(Process p)
{
long result = -1;
try
{
//for windows
if (p.getClass().getName().equals("java.lang.Win32Process") ||
p.getClass().getName().equals("java.lang.ProcessImpl"))
{
Field f = p.getClass().getDeclaredField("handle");
f.setAccessible(true);
long handl = f.getLong(p);
Kernel32 kernel = Kernel32.INSTANCE;
WinNT.HANDLE hand = new WinNT.HANDLE();
hand.setPointer(Pointer.createConstant(handl));
result = kernel.GetProcessId(hand);
f.setAccessible(false);
}
//for unix based operating systems
else if (p.getClass().getName().equals("java.lang.UNIXProcess"))
{
Field f = p.getClass().getDeclaredField("pid");
f.setAccessible(true);
result = f.getLong(p);
f.setAccessible(false);
}
}
catch(Exception ex)
{
result = -1;
}
return result;
}
Вы также можете загрузить JNA из здесь и JNA Platform из здесь.
Я думаю, что нашел решение, которое выглядит довольно пуленепробивным, работая на большинстве платформ. Вот идея:
Поскольку вы проверяете только дочерние процессы, вы не можете быть допущены каким-либо другим процессом на той же машине. Мьютекс в формате JVM, который позволяет вам быть уверенным, что новый процесс является правильным.
Чтение списка дочерних процессов проще, чем получение PID из объектов процесса, поскольку для него не требуются вызовы WIN API в Windows и, что более важно, это уже сделано в нескольких библиотеках.
Ниже приведена реализация вышеупомянутой идеи с использованием JavaSysMon библиотеки. Это
class UDKSpawner {
private int uccPid;
private Logger uccLog;
/**
* Mutex that forces only one child process to be spawned at a time.
*
*/
private static final Object spawnProcessMutex = new Object();
/**
* Spawns a new UDK process and sets {@link #uccPid} to it PID. To work correctly,
* the code relies on the fact that no other method in this JVM runs UDK processes and
* that no method kills a process unless it acquires lock on spawnProcessMutex.
* @param procBuilder
* @return
*/
private Process spawnUDK(ProcessBuilder procBuilder) throws IOException {
synchronized (spawnProcessMutex){
JavaSysMon monitor = new JavaSysMon();
DirectUDKChildProcessVisitor beforeVisitor = new DirectUDKChildProcessVisitor();
monitor.visitProcessTree(monitor.currentPid(), beforeVisitor);
Set<Integer> alreadySpawnedProcesses = beforeVisitor.getUdkPids();
Process proc = procBuilder.start();
DirectUDKChildProcessVisitor afterVisitor = new DirectUDKChildProcessVisitor();
monitor.visitProcessTree(monitor.currentPid(), afterVisitor);
Set<Integer> newProcesses = afterVisitor.getUdkPids();
newProcesses.removeAll(alreadySpawnedProcesses);
if(newProcesses.isEmpty()){
uccLog.severe("There is no new UKD PID.");
}
else if(newProcesses.size() > 1){
uccLog.severe("Multiple new candidate UDK PIDs");
} else {
uccPid = newProcesses.iterator().next();
}
return proc;
}
}
private void killUDKByPID(){
if(uccPid < 0){
uccLog.severe("Cannot kill UCC by PID. PID not set.");
return;
}
synchronized(spawnProcessMutex){
JavaSysMon monitor = new JavaSysMon();
monitor.killProcessTree(uccPid, false);
}
}
private static class DirectUDKChildProcessVisitor implements ProcessVisitor {
Set<Integer> udkPids = new HashSet<Integer>();
@Override
public boolean visit(OsProcess op, int i) {
if(op.processInfo().getName().equals("UDK.exe")){
udkPids.add(op.processInfo().getPid());
}
return false;
}
public Set<Integer> getUdkPids() {
return udkPids;
}
}
}
В моем тестировании все классы IMPL имели поле "pid". Это сработало для меня:
public static int getPid(Process process) {
try {
Class<?> cProcessImpl = process.getClass();
Field fPid = cProcessImpl.getDeclaredField("pid");
if (!fPid.isAccessible()) {
fPid.setAccessible(true);
}
return fPid.getInt(process);
} catch (Exception e) {
return -1;
}
}
Просто убедитесь, что возвращаемое значение не равно -1. Если это так, то проанализируйте вывод ps
.
Я использовал непереносимый подход для извлечения идентификатора UNIX из объекта Process
, который очень прост.
ШАГ 1:
Используйте некоторые вызовы API Reflection, чтобы идентифицировать класс реализации Process
на целевом сервере JRE (помните, что Process
является абстрактным классом). Если ваша реализация UNIX похожа на мою, вы увидите класс реализации, который имеет свойство с именем pid
, которое содержит PID процесса. Вот код регистрации, который я использовал.
//--------------------------------------------------------------------
// Jim Tough - 2014-11-04
// This temporary Reflection code is used to log the name of the
// class that implements the abstract Process class on the target
// JRE, all of its 'Fields' (properties and methods) and the value
// of each field.
//
// I only care about how this behaves on our UNIX servers, so I'll
// deploy a snapshot release of this code to a QA server, run it once,
// then check the logs.
//
// TODO Remove this logging code before building final release!
final Class<?> clazz = process.getClass();
logger.info("Concrete implementation of " + Process.class.getName() +
" is: " + clazz.getName());
// Array of all fields in this class, regardless of access level
final Field[] allFields = clazz.getDeclaredFields();
for (Field field : allFields) {
field.setAccessible(true); // allows access to non-public fields
Class<?> fieldClass = field.getType();
StringBuilder sb = new StringBuilder(field.getName());
sb.append(" | type: ");
sb.append(fieldClass.getName());
sb.append(" | value: [");
Object fieldValue = null;
try {
fieldValue = field.get(process);
sb.append(fieldValue);
sb.append("]");
} catch (Exception e) {
logger.error("Unable to get value for [" +
field.getName() + "]", e);
}
logger.info(sb.toString());
}
//--------------------------------------------------------------------
ШАГ 2:
Основываясь на классе реализации и имени поля, полученном вами в журнале Reflection, напишите некоторый код для pickpocket класса реализации Process
и извлеките PID из него с помощью Reflection API. Код ниже работает для меня по моему вкусу UNIX. Возможно, вам придется настроить константы EXPECTED_IMPL_CLASS_NAME
и EXPECTED_PID_FIELD_NAME
, чтобы они работали для вас.
/**
* Get the process id (PID) associated with a {@code Process}
* @param process {@code Process}, or null
* @return Integer containing the PID of the process; null if the
* PID could not be retrieved or if a null parameter was supplied
*/
Integer retrievePID(final Process process) {
if (process == null) {
return null;
}
//--------------------------------------------------------------------
// Jim Tough - 2014-11-04
// NON PORTABLE CODE WARNING!
// The code in this block works on the company UNIX servers, but may
// not work on *any* UNIX server. Definitely will not work on any
// Windows Server instances.
final String EXPECTED_IMPL_CLASS_NAME = "java.lang.UNIXProcess";
final String EXPECTED_PID_FIELD_NAME = "pid";
final Class<? extends Process> processImplClass = process.getClass();
if (processImplClass.getName().equals(EXPECTED_IMPL_CLASS_NAME)) {
try {
Field f = processImplClass.getDeclaredField(
EXPECTED_PID_FIELD_NAME);
f.setAccessible(true); // allows access to non-public fields
int pid = f.getInt(process);
return pid;
} catch (Exception e) {
logger.warn("Unable to get PID", e);
}
} else {
logger.warn(Process.class.getName() + " implementation was not " +
EXPECTED_IMPL_CLASS_NAME + " - cannot retrieve PID" +
" | actual type was: " + processImplClass.getName());
}
//--------------------------------------------------------------------
return null; // If PID was not retrievable, just return null
}
Это не общий ответ.
Однако: Некоторые программы, особенно сервисы и длительные программы, создают (или предлагают создать, необязательно) "файл pid".
Например, LibreOffice предлагает --pidfile={file}
, см. docs.
Я искал достаточно времени для решения Java/Linux, но PID был (в моем случае) под рукой.
Нет простого решения. То, как я это делал в прошлом, - это запустить другой процесс для запуска команды ps
в Unix-подобных системах или команды tasklist
в Windows, а затем проанализировать вывод этой команды для PID я хотеть. На самом деле, я вложил этот код в отдельную оболочку script для каждой платформы, которая только что вернула PID, чтобы я мог оставить кусок Java как можно более независимым от платформы. Это не работает для краткосрочных задач, но это не проблема для меня.
jnr-process предоставляет эту возможность.
Это часть встроенной среды java, используемой jruby и может считаться прототипом будущего java-FFI
Я считаю, что единственный переносимый способ сделать это - запустить (дочерний) процесс через другой (родительский) Java-процесс, который сообщит мне фактический PID родительского процесса. Детский процесс может быть любым.
Код этой обертки
package com.panayotis.wrapper;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
System.out.println(ManagementFactory.getRuntimeMXBean().getName().split("@")[0]);
ProcessBuilder pb = new ProcessBuilder(args);
pb.directory(new File(System.getProperty("user.dir")));
pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.start().waitFor();
}
}
Чтобы использовать его, создайте файл jar только с этим именем и вызовите его с помощью аргументов команды:
String java = System.getProperty("java.home") + separator + "bin" + separator + "java.exe";
String jar_wrapper = "path\\of\\wrapper.jar";
String[] args = new String[]{java, "-cp", jar_wrapper, "com.panayotis.wrapper.Main", actual_exec_args...);
Если переносимость не вызывает беспокойства, и вы просто хотите получить pid на Windows без особых хлопот при использовании кода, который проверен и, как известно, работает во всех современных версиях Windows, вы можете использовать kohsuke winp. Он также доступен на Maven Central для легкого использования.
Process process = //...;
WinProcess wp = new WinProcess(process);
int pid = wp.getPid();
Существует библиотека с открытым исходным кодом, которая имеет такую функцию, и имеет кросс-платформенную реализацию: https://github.com/OpenHFT/Java-Thread-Affinity
Возможно, слишком сложно получить PID, но если вам нужны другие вещи, такие как идентификатор процессора и потока, и, в частности, привязка к потоку, он может быть вам адекватен.
Чтобы получить текущий идентификатор потока, просто вызовите Affinity.getAffinityImpl().getProcessId()
.
Это реализовано с использованием JNA (см. ответ arcsin).
Одним из решений является использование уникальных инструментов, которые предлагает платформа:
private static String invokeLinuxPsProcess(String filterByCommand) {
List<String> args = Arrays.asList("ps -e -o stat,pid,unit,args=".split(" +"));
// Example output:
// Sl 22245 bpds-api.service /opt/libreoffice5.4/program/soffice.bin --headless
// Z 22250 - [soffice.bin] <defunct>
try {
Process psAux = new ProcessBuilder(args).redirectErrorStream(true).start();
try {
Thread.sleep(100); // TODO: Find some passive way.
} catch (InterruptedException e) { }
try (BufferedReader reader = new BufferedReader(new InputStreamReader(psAux.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
if (!line.contains(filterByCommand))
continue;
String[] parts = line.split("\\w+");
if (parts.length < 4)
throw new RuntimeException("Unexpected format of the `ps` line, expected at least 4 columns:\n\t" + line);
String pid = parts[1];
return pid;
}
}
}
catch (IOException ex) {
log.warn(String.format("Failed executing %s: %s", args, ex.getMessage()), ex);
}
return null;
}
Отказ от ответственности: не проверен, но вы получаете идею:
ps
, чтобы перечислить процессы,ps
.Для GNU/Linux & Системы MacOS (или вообще UNIX-подобные), я использовал метод ниже, который отлично работает:
private int tryGetPid(Process process)
{
if (process.getClass().getName().equals("java.lang.UNIXProcess"))
{
try
{
Field f = process.getClass().getDeclaredField("pid");
f.setAccessible(true);
return f.getInt(process);
}
catch (IllegalAccessException | IllegalArgumentException | NoSuchFieldException | SecurityException e)
{
}
}
return 0;
}
Использование JNA, поддержка старой и новой JVM для получения идентификатора процесса
public static long getProcessId(Process p){
long pid = -1;
try {
pid = p.pid();
} catch (NoSuchMethodError e) {
try
{
//for windows
if (p.getClass().getName().equals("java.lang.Win32Process") || p.getClass().getName().equals("java.lang.ProcessImpl")) {
Field f = p.getClass().getDeclaredField("handle");
f.setAccessible(true);
long handl = f.getLong(p);
Kernel32 kernel = Kernel32.INSTANCE;
WinNT.HANDLE hand = new WinNT.HANDLE();
hand.setPointer(Pointer.createConstant(handl));
pid = kernel.GetProcessId(hand);
f.setAccessible(false);
}
//for unix based operating systems
else if (p.getClass().getName().equals("java.lang.UNIXProcess"))
{
Field f = p.getClass().getDeclaredField("pid");
f.setAccessible(true);
pid = f.getLong(p);
f.setAccessible(false);
}
}
catch(Exception ex)
{
pid = -1;
}
}
return pid;
}
Вы можете извлечь pid из среды выполнения bean, как в:
ManagementFactory.getRuntimeMXBean().getName()
Он состоит из pid @hostname, поэтому вы можете легко получить pid с помощью простого регулярного выражения.
Для информации о времени выполнения (свободная память, загрузка, доступный cpus и т.д.) вы можете использовать:
Runtime.getRuntime()