Java程序如何获取自身的进程ID?

423

如何获取Java进程的id?

我知道有几种平台相关的hack方法,但我更倾向于一种更通用的解决方案。


1
一个关于Linux的问题链接:如何在Java或JRuby上找到我的PID? - Jarekczek
5
计划在JDK9中修复此问题。http://openjdk.java.net/jeps/102 - Andrew
在Java SE10中实现的方法 - mrsrinivas
另一种方法是创建JNI库。 - zomega
22个回答

420

没有一种平台无关的方式可以保证在所有jvm实现中都能正常工作。 ManagementFactory.getRuntimeMXBean().getName() 看起来是最好(最接近)的解决方案,通常包括PID。它很简短,并且 可能 在广泛使用的每个实现中都有效。

在linux + windows上,它返回类似于 "12345@hostname" 的值(其中12345是进程ID)。但请注意,根据文档,对于这个值没有任何保证:

返回表示运行的Java虚拟机的名称。返回的名称字符串可以是任意字符串,Java虚拟机实现可以选择在返回的名称字符串中嵌入特定于平台的有用信息。每个正在运行的虚拟机可能具有不同的名称。

在Java 9中可以使用新的进程API

long pid = ProcessHandle.current().pid();

2
这个解决方案非常脆弱。请参见下面有关Hyperic Sigar的答案。 - Michael Klishin
将该pid写入锁文件是不错的选择,参考自https://dev59.com/0HVC5IYBdhLWcg3w-mVO#9020391。 - Aquarius Power
2
@MichaelKlishin 在哪方面是脆弱的? - Eugene

131

你可以使用JNA。不幸的是,目前还没有通用的JNA API来获取当前进程ID,但每个平台都非常简单:

Windows

确保你有jna-platform.jar,然后:

int pid = Kernel32.INSTANCE.GetCurrentProcessId();

Unix

声明:

private interface CLibrary extends Library {
    CLibrary INSTANCE = (CLibrary) Native.loadLibrary("c", CLibrary.class);   
    int getpid ();
}

那么:

int pid = CLibrary.INSTANCE.getpid();

Java 9

在Java 9中,可以使用新的进程API来获取当前进程的ID。首先获取当前进程的句柄,然后查询PID:

long pid = ProcessHandle.current().pid();

4
+1 但是在安全受限的环境中,它可能无法正常工作(如Tomcat、WebLogic等)。 - ATorras
1
getpid() 解决方案同样适用于 OS X,只需通过 JNA 调用 "System" 库即可。 - Daniel Widdis
然而,我在jdk9中遇到了“错误:找不到符号”的问题。 - skytree

63

这里有一个后门方法,可能不适用于所有虚拟机,但应该在Linux和Windows上都能使用(原始示例在此处):

java.lang.management.RuntimeMXBean runtime = 
    java.lang.management.ManagementFactory.getRuntimeMXBean();
java.lang.reflect.Field jvm = runtime.getClass().getDeclaredField("jvm");
jvm.setAccessible(true);
sun.management.VMManagement mgmt =  
    (sun.management.VMManagement) jvm.get(runtime);
java.lang.reflect.Method pid_method =  
    mgmt.getClass().getDeclaredMethod("getProcessId");
pid_method.setAccessible(true);

int pid = (Integer) pid_method.invoke(mgmt);

2
非常好,刚刚验证了它在JRockit和Hotspot JVM上都可以工作。 - Drupad Panchal
1
不错的解决方法。我会假设这个方法(以及类中的其他方法)没有公开和易于访问的好理由,我很想知道是什么原因。 - fragorl
2
Oracle Java 团队宣布他们打算隐藏所有非 Java 包(比如 sun.*)我相信这将从 Java 10 开始(计划于2018年 - 也许是Java 9)。如果您有与上述实现类似的实现,您可能需要想出一种替代方案,以避免您的代码发生故障。 - hfontanez
我也无法使用,因为sun.*包可能已被删除或无法访问(VMManagement无法解析为类型)。 - Mike Stoddart
1
由于反射的工作方式,不需要访问sun.management.*。只需对jvm.get(runtime)返回的对象执行getDeclaredMethod即可。 - Andrew T Finnell

36

试试Sigar,它有非常广泛的API。 Apache 2许可证。

private Sigar sigar;

public synchronized Sigar getSigar() {
    if (sigar == null) {
        sigar = new Sigar();
    }
    return sigar;
}

public synchronized void forceRelease() {
    if (sigar != null) {
        sigar.close();
        sigar = null;
    }
}

public long getPid() {
    return getSigar().getPid();
}

6
如果您解释一下如何使用SIGAR,您将获得更多的赞同。 - Brad Mace
1
链接已失效。您提供了一个如何使用它的示例,但是否有适用于 Maven 的依赖项? - Jules
3
https://github.com/hyperic/sigar 似乎是一个可用的位置;这也表明它是具有Java绑定的本地代码,这可能使它在许多情况下不太适合解决方案。 - Zastai

30
以下方法尝试从 java.lang.management.ManagementFactory 中提取PID:
private static String getProcessId(final String fallback) {
    // Note: may fail in some JVM implementations
    // therefore fallback has to be provided

    // something like '<pid>@<hostname>', at least in SUN / Oracle JVMs
    final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
    final int index = jvmName.indexOf('@');

    if (index < 1) {
        // part before '@' empty (index = 0) / '@' not found (index = -1)
        return fallback;
    }

    try {
        return Long.toString(Long.parseLong(jvmName.substring(0, index)));
    } catch (NumberFormatException e) {
        // ignore
    }
    return fallback;
}

只需调用getProcessId("<PID>")函数即可。


8
VisualVM使用类似的代码来获取自身PID,请查看com.sun.tools.visualvm.application.jvm.Jvm#ApplicationSupport#createCurrentApplication()。由于他们是专家,因此这看起来是一个可靠的、跨平台的解决方案。 - Espinosa
@Espinosa VisualVM仅支持在OpenJDK/Oracle/GraalVM JVM上运行(截至2020年)。这意味着他们可以相应地做出假设。如果您也这样做,您将变得依赖于供应商,如果在例如J9上运行,则可能会导致代码崩溃。 - Thorbjørn Ravn Andersen

25

For older JVM, in linux...

private static String getPid() throws IOException {
    byte[] bo = new byte[256];
    InputStream is = new FileInputStream("/proc/self/stat");
    is.read(bo);
    for (int i = 0; i < bo.length; i++) {
        if ((bo[i] < '0') || (bo[i] > '9')) {
            return new String(bo, 0, i);
        }
    }
    return "-1";
}

58
如果你打算这样做,那就直接执行int pid = Integer.parseInt(new File("/proc/self").getCanonicalFile().getName());。为什么要多此一举呢? - David Citron
2
对于好奇的人,@David评论中的技巧确实有效。有人能说一下原因吗?是 getCanonicalFile() 方法中的某种魔法将“self”转换为PID吗? - GreenGiant
9
getCanonicalFile()方法会解析符号链接 /proc/self/ -> /proc/1234,可以尝试通过输入命令 ls -al /proc/self 来查看。 - Michael
2
@DavidCitron +1!你是对的,我没有考虑到 getCanonicalFile :-) - ggrandes

22

自Java 9以来,有一个Process.getPid()方法,它返回进程的本地ID:

public abstract class Process {

    ...

    public long getPid();
}

要获取当前Java进程的进程ID,可以使用ProcessHandle接口:

System.out.println(ProcessHandle.current().pid());

2
你能解释一下如何在Java 9中获取当前正在运行的进程的Process实例吗? - Augusto
使用Java 9的好答案,非常适用于2016年!您能添加一个指向javadocs的链接吗?谢谢。 - crazyGuy

18
您可以查看我的项目:JavaSysMon 在 GitHub 上。 它跨平台提供进程 ID 和一堆其他内容(CPU 使用率,内存使用情况)(目前支持 Windows、Mac OSX、Linux 和 Solaris)。

能否获取由Java启动的进程的PID?或者以“你的方式”启动一个进程来解决这个问题? - Panayotis

12
java.lang.management.ManagementFactory.getRuntimeMXBean().getName().split("@")[0]

10

在Scala中:

import sys.process._
val pid: Long = Seq("sh", "-c", "echo $PPID").!!.trim.toLong

这应该能在Unix系统上为您提供解决方法,直到Java 9发布。(我知道,问题是关于Java的,但由于Scala没有等效的问题,我想留下这个问题给可能遇到相同问题的Scala用户。)


看起来适用于Linux和macOS,但我不确定它是否适用于Windows。 - Seth Tisue
启动一个全新的子进程只是为了获得已经在该进程本身中可用的PID,这非常浪费。而且,这种方法在Windows上不起作用,因为它没有“sh”命令(通常情况下)。 - toolforger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接