Development Tip

Java 프로그램 내에서 방금 시작한 프로세스의 PID를 얻는 방법은 무엇입니까?

yourdevel 2020. 11. 14. 11:11
반응형

Java 프로그램 내에서 방금 시작한 프로세스의 PID를 얻는 방법은 무엇입니까?


다음 코드로 프로세스를 시작했습니다.

 ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "path");
 try {
     Process p = pb.start();       
 } 
 catch (IOException ex) {}

이제 방금 시작한 프로세스의 PID를 알아야합니다.


아직 공개 API가 없습니다. Sun Bug 4244896 , Sun Bug 4250622 참조

해결 방법 :

Runtime.exec(...)

유형의 개체를 반환합니다.

java.lang.Process

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"의 인스턴스를 반환합니다.

둘 다 개인 필드 "핸들"이 있습니다.

프로세스의 OS 핸들입니다. 이 + Win32 API를 사용하여 PID를 쿼리해야합니다. 이 페이지에는 그 방법에 대한 세부 정보가 있습니다.


때문에 자바 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 플랫폼")과이 기능을 사용 :

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을 다운로드 할 수도 있습니다 .


대부분의 플랫폼에서 작업하는 동안 매우 방탄 해 보이는 솔루션을 찾은 것 같습니다. 아이디어는 다음과 같습니다.

  1. 새 프로세스 생성 / 프로세스 종료 전에 획득 한 JVM 전체 뮤텍스를 생성합니다.
  2. 플랫폼 종속 코드를 사용하여 하위 프로세스 목록 + JVM 프로세스의 pid를 얻습니다.
  3. 새 프로세스 생성
  4. 하위 프로세스 + pid의 새 목록을 획득하고 이전 목록과 비교합니다. 새로운 것은 당신의 남자입니다.

하위 프로세스 만 확인하기 때문에 동일한 시스템의 다른 프로세스에 의해 잘못 될 수 없습니다. JVM 전체 뮤텍스는 새 프로세스가 올바른지 확인할 수 있도록합니다.

자식 프로세스 목록을 읽는 것은 프로세스 개체에서 PID를 가져 오는 것보다 간단합니다. Windows에서 WIN API 호출이 필요하지 않고 더 중요한 것은 이미 여러 라이브러리에서 수행 되었기 때문입니다.

아래는 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's 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.


나는 이식이 불가능한 접근 방식을 사용 Process하여 추적하기 매우 쉬운 객체 에서 UNIX PID를 검색했습니다 .

1 단계 : 일부 Reflection API 호출을 사용 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 로깅에서 얻은 구현 클래스 및 필드 이름을 기반으로 일부 코드를 작성하여 Process구현 클래스 를 소매치기 하고 Reflection API를 사용하여 PID를 검색합니다. 아래 코드는 내 취향의 UNIX에서 나를 위해 작동합니다. 작동하도록 하려면 EXPECTED_IMPL_CLASS_NAMEEXPECTED_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} 참조하십시오 .

Java / Linux 솔루션에 대해 꽤 오랜 시간을 찾고 있었지만 PID는 (제 경우에는) 가까이에있었습니다.


간단한 해결책은 없습니다. 과거에 내가 한 방식은 psUnix 계열 시스템 에서 명령 을 실행하거나 tasklistWindows 에서 명령 을 실행하는 다른 프로세스를 시작한 다음 원하는 PID에 대해 해당 명령의 출력을 구문 분석하는 것입니다. 실제로 저는 해당 코드를 방금 PID를 반환 한 각 플랫폼에 대한 별도의 셸 스크립트에 넣어서 Java 조각을 가능한 한 플랫폼 독립적으로 유지할 수있었습니다. 이것은 단기 작업에는 잘 작동하지 않지만 나에게는 문제가되지 않았습니다.


JNR 프로세스 프로젝트는이 기능을 제공합니다.

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...);

이식성이 문제가되지 않고 모든 최신 버전의 Windows에서 작동하는 것으로 알려진 코드를 사용하는 동안 많은 번거 로움없이 Windows에서 pid를 얻으려면 kohsuke의 winp 라이브러리를 사용할 수 있습니다 . Maven Central에서도 쉽게 사용할 수 있습니다.

Process process = //...;
WinProcess wp = new WinProcess(process);
int pid = wp.getPid();

이러한 기능을 가진 오픈 소스 라이브러리가 있으며 크로스 플랫폼 구현이 있습니다 : https://github.com/OpenHFT/Java-Thread-Affinity

It may be overkill just to get the PID, but if you want other things like CPU and thread id, and specifically thread affinity, it may be adequate for you.

To get the current thread's PID, just call Affinity.getAffinityImpl().getProcessId().

This is implemented using JNA (see arcsin's answer).


One solution is to use the idiosyncratic tools the platform offers:

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;
}

Disclaimer: Not tested, but you get the idea:

  • Call ps to list the processes,
  • Find your one because you know the command you launched it with.
  • If there are multiple processes with the same command, you can:
    • Add another dummy argument to differentiate them
    • Rely on the increasing PID (not really safe, not concurrent)
    • Check the time of process creation (could be too coarse to really differentiate, also not concurrent)
    • Add a specific environment variable and list it with ps too.

For GNU/Linux & MacOS (or generally UNIX like) systems, I've used below method which works fine:

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;
}

Using JNA, supporting old and new JVM to get process id

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;
}

참고URL : https://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program

반응형