天天看點

Sun的JDK裡擷取目前程序ID的方法(hack)【轉】

Java标準庫裡常見的公有API确實是沒有擷取目前程序的ID的方法,有時候挺郁悶的,就是需要自己的PID。

于是有各種workaround,其中有很靠譜的通過JNI調用外部的C/C++擴充,然後調用作業系統提供的相應API去擷取PID;也有些不怎麼靠譜的hack。這裡要介紹的就是後者之一,隻在Sun JDK或相容的JDK上有效的方法。

代碼例子如下:

Java代碼

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: "[email protected]"
        try {
            return Integer.parseInt(name.substring(0, name.indexOf('@')));
        } catch (Exception e) {
            return -1;
        }
    }
}
           

使用Sun JDK裡RuntimeMXBean.getName()方法的實作,可以很輕松的拿到自身的PID。

運作這個程式可以看到類似以下的輸出:

Command prompt代碼

D:\experiment>java ShowOwnPID
pid: 11704      

這個時候再跑一個jps來看看目前都有哪些Java程序的話,可以看到:

Command prompt代碼

D:\experiment>jps
7888 Jps
11704 ShowOwnPID      

嗯……這PID沒錯。

這PID是哪兒來的呢?先看RuntimeMXBean執行個體的來源,java.lang.management.ManagementFactory:

Java代碼

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,于是看看對應的實作:

Java代碼

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。它的實作是:

Java代碼

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()的傳回值,再跟過去看:

Java代碼

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()傳回過來的字元串确實是"[email protected]"形式的。再追下去,看看native一側是如何擷取PID的話:

j2se/src/share/native/sun/management/VMManagementImpl.c

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

C++代碼

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

C++代碼

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上是:

C++代碼

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的辦法,才得知原來以前有人總結過幾種辦法 ,其中第一個就是本文提到的這個。嘛,需求是一直有的,這種功能自然是早該有人搗騰過了。

轉自:http://rednaxelafx.iteye.com/blog/716918