Sun的JDK里获取当前进程ID的方法(hack)

Java标准库里常见的公有API确实是没有获取当前进程的ID的方法,有时候挺郁闷的,就是需要自己的PID。
于是有各种workaround,其中有很靠谱的通过JNI调用外部的C/C++扩展,然后调用操作系统提供的相应API去获取PID;也有些不怎么靠谱的hack。这里要介绍的就是后者之一,只在Sun JDK或兼容的JDK上有效的方法。

代码例子如下:
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;

public class ShowOwnPID {
public static void main(String[] args) throws Exception {
int pid = getPid();
System.out.println("pid: " + pid);
System.in.read(); // block the program so that we can do some probing on it
}

private static int getPid() {
RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
String name = runtime.getName(); // format: "pid@hostname"
try {
return Integer.parseInt(name.substring(0, name.indexOf('@')));
} catch (Exception e) {
return -1;
}
}
}

使用Sun JDK里RuntimeMXBean.getName()方法的实现,可以很轻松的拿到自身的PID。
运行这个程序可以看到类似以下的输出:
D:\experiment>java ShowOwnPID
pid: 11704

这个时候再跑一个jps来看看当前都有哪些Java进程的话,可以看到:
D:\experiment>jps
7888 Jps
11704 ShowOwnPID

嗯……这PID没错。

这PID是哪儿来的呢?先看RuntimeMXBean实例的来源,java.lang.management.ManagementFactory:
package java.lang.management;

// ...

public class ManagementFactory {
// ...

/**
* Returns the managed bean for the runtime system of
* the Java virtual machine.
*
* @return a {@link RuntimeMXBean} object for the Java virtual machine.

*/
public static RuntimeMXBean getRuntimeMXBean() {
return sun.management.ManagementFactory.getRuntimeMXBean();
}

// ...
}

可以看到它依赖了sun.management.ManagementFactory,于是看看对应的实现:
package sun.management;

import java.lang.management.*;
// ...
import static java.lang.management.ManagementFactory.*;

public class ManagementFactory {
private ManagementFactory() {};

private static VMManagement jvm;

private static RuntimeImpl runtimeMBean = null;

public static synchronized RuntimeMXBean getRuntimeMXBean() {
if (runtimeMBean == null) {
runtimeMBean = new RuntimeImpl(jvm);
}
return runtimeMBean;
}

static {
AccessController.doPrivileged(new LoadLibraryAction("management"));
jvm = new VMManagementImpl();
}

// ...
}

这里可以发现实现RuntimeMXBean接口的是sun.management.RuntimeImpl。它的实现是:
package sun.management;

import java.lang.management.RuntimeMXBean;
// ...

/**
* Implementation class for the runtime subsystem.
* Standard and committed hotspot-specific metrics if any.
*
* ManagementFactory.getRuntimeMXBean() returns an instance
* of this class.
*/
class RuntimeImpl implements RuntimeMXBean {
private final VMManagement jvm;
private final long vmStartupTime;

/**
* Constructor of RuntimeImpl class.
*/
RuntimeImpl(VMManagement vm) {
this.jvm = vm;
this.vmStartupTime = jvm.getStartupTime();
}

public String getName() {
return jvm.getVmId();
}

// ...
}

OK,看到getName()返回的是VMManagement.getVmId()的返回值,再跟过去看:
package sun.management;

import java.net.InetAddress;
import java.net.UnknownHostException;
// ...

class VMManagementImpl implements VMManagement {
// ...

public String getVmId() {
int pid = getProcessId();
String hostname = "localhost";
try {
hostname = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
// ignore
}

return pid + "@" + hostname;
}
private native int getProcessId();

// ...
}

OK,这里可以看到getVmId()返回过来的字符串确实是"pid@hostname"形式的。再追下去,看看native一侧是如何获取PID的话:
j2se/src/share/native/sun/management/VMManagementImpl.c
JNIEXPORT jint JNICALL
Java_sun_management_VMManagementImpl_getProcessId
(JNIEnv *env, jobject dummy)
{
jlong pid = jmm_interface->GetLongAttribute(env, NULL,
JMM_OS_PROCESS_ID);
return (jint) pid;
}

于是继续跟,
hotspot/src/share/vm/services/management.cpp
static jlong get_long_attribute(jmmLongAttribute att) {
switch (att) {
// ...
case JMM_OS_PROCESS_ID:
return os::current_process_id();

// ...

default:
return -1;
}
}

JVM_ENTRY(jlong, jmm_GetLongAttribute(JNIEnv *env, jobject obj, jmmLongAttribute att))
if (obj == NULL) {
return get_long_attribute(att);
} else {
// ...
}
return -1;
JVM_END


接下来os::current_process_id()的实现就是每个操作系统不同的了。
在Linux上是:
hotspot/src/os/linux/vm/os_linux.cpp
static pid_t _initial_pid = 0;

int os::current_process_id() {

// Under the old linux thread library, linux gives each thread
// its own process id. Because of this each thread will return
// a different pid if this method were to return the result
// of getpid(2). Linux provides no api that returns the pid
// of the launcher thread for the vm. This implementation
// returns a unique pid, the pid of the launcher thread
// that starts the vm 'process'.

// Under the NPTL, getpid() returns the same pid as the
// launcher thread rather than a unique pid per thread.
// Use gettid() if you want the old pre NPTL behaviour.

// if you are looking for the result of a call to getpid() that
// returns a unique pid for the calling thread, then look at the
// OSThread::thread_id() method in osThread_linux.hpp file

return (int)(_initial_pid ? _initial_pid : getpid());
}

// this is called _before_ the most of global arguments have been parsed
void os::init(void) {

// With LinuxThreads the JavaMain thread pid (primordial thread)
// is different than the pid of the java launcher thread.
// So, on Linux, the launcher thread pid is passed to the VM
// via the sun.java.launcher.pid property.
// Use this property instead of getpid() if it was correctly passed.
// See bug 6351349.
pid_t java_launcher_pid = (pid_t) Arguments::sun_java_launcher_pid();

_initial_pid = (java_launcher_pid > 0) ? java_launcher_pid : getpid();
// ...
}


在Windows上是:

static int _initial_pid = 0;

int os::current_process_id()
{
return (_initial_pid ? _initial_pid : _getpid());
}

// this is called _before_ the global arguments have been parsed
void os::init(void) {
_initial_pid = _getpid();
// ...
}


=================================================================

好吧其实我是在HotSpot的源码里搜pid然后慢慢找出JMM代码里有调用过os::current_process_id(),然后才一步步向上找到对应的Java API。刚才问毕玄老大有没有见过在Java代码里获取PID的办法,才得知原来[url=http://blog.igorminar.com/2007/03/how-java-application-can-discover-its.html]以前有人总结过几种办法[/url],其中第一个就是本文提到的这个。嘛,需求是一直有的,这种功能自然是早该有人捣腾过了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值