5.AttachAPIのVM側 - jconsoleの仕組み

LinuxAttachProviderの生成

pidを指定してインスタンスを生成している.

// jdk/src/solaris/classes/sun/tools/attach/LinuxVirtualMachine.java:52
    LinuxVirtualMachine(AttachProvider provider, String vmid)
        throws AttachNotSupportedException, IOException
    {
//...
        // Find the socket file. If not found then we attempt to start the
        // attach mechanism in the target VM by sending it a QUIT signal.
        // Then we attempt to find the socket file again.
        path = findSocketFile(pid);
        if (path == null) {
            File f = createAttachFile(pid);
            try {
                // On LinuxThreads each thread is a process and we don't have the
                // pid of the VMThread which has SIGQUIT unblocked. To workaround
                // this we get the pid of the "manager thread" that is created
                // by the first call to pthread_create. This is parent of all
                // threads (except the initial thread).
//...
                    sendQuitTo(pid);
//...
                    try {
                        Thread.sleep(delay);
                    } catch (InterruptedException x) { }
                    path = findSocketFile(pid);
                    i++;
//...
        }

socketファイルを探す.
なかったらプロセスにSIGQUITを送って,ソケットファイルが出来るのをまつ.
 

VM側でのソケットファイル作成

きっとHotSpotで生成しているはず.

    private File createAttachFile(int pid) throws IOException {
        String fn = ".attach_pid" + pid;
        String path = "/proc/" + pid + "/cwd/" + fn;

で生成しているファイル名でgrepしてみる.
 

openjdk-6-src/hotspot$ grep -Hne ".attach_pid" ./**/**
./src/os/linux/vm/attachListener_linux.cpp:460:// If the file .attach_pid<pid> exists in the working directory
./src/os/linux/vm/attachListener_linux.cpp:467:  sprintf(fn, ".attach_pid%d", os::current_process_id());
./src/os/linux/vm/attachListener_linux.cpp:472:    sprintf(fn, "/tmp/.attach_pid%d", os::current_process_id());

この辺っぽい.
 

//hotspot/src/os/linux/vm/attachListener_linux.cpp:56
bool AttachListener::is_init_trigger() {
  if (init_at_startup() || is_initialized()) {
    return false;               // initialized at startup or already initialized
  }
  char fn[32];
  sprintf(fn, ".attach_pid%d", os::current_process_id());
  int ret;
  struct stat64 st;
  RESTARTABLE(::stat64(fn, &st), ret);
  if (ret == -1) {
    sprintf(fn, "/tmp/.attach_pid%d", os::current_process_id());
    RESTARTABLE(::stat64(fn, &st), ret);
  }
  if (ret == 0) {
    // simple check to avoid starting the attach mechanism when
    // a bogus user creates the file
    if (st.st_uid == geteuid()) {
      init();
      return true;
    }
  }
  return false;
}

stat64でファイルが存在するか調べている.ファイルが無ければinit()を呼び出す.
 

//hotspot/src/os/linux/vm/attachListener_linux.cpp:84
int LinuxAttachListener::init() {
//...
  // create the listener socket
  listener = ::socket(PF_UNIX, SOCK_STREAM, 0);

あぁ,やっぱりUnixドメインソケットなのか.
この関数はどこで呼び出されるか

ソケットの生成タイミング

// SIGBREAK is sent by the keyboard to query the VM state
#ifndef SIGBREAK
#define SIGBREAK SIGQUIT
#endif

static void signal_thread_entry(JavaThread* thread, TRAPS) {
  os::set_priority(thread, NearMaxPriority);  
  while (true) {
//...
    switch (sig) {
      case SIGBREAK: {
        // Check if the signal is a trigger to start the Attach Listener - in that
        // case don't print stack traces.
	if (!DisableAttachMechanism && AttachListener::is_init_trigger()) {
	  continue;
  	}
//...

なるほど.SIGQUITを送ったタイミングで生成するわけか.
さて,次はどこで作成したソケットをどこでacceptしてるか.
 

Unixドメインソケットのaccept

//hotspot/src/os/linux/vm/attachListener_linux.cpp:317
// Dequeue an operation
//
// In the Linux implementation there is only a single operation and clients
// cannot queue commands (except at the socket level). 
//
LinuxAttachOperation* LinuxAttachListener::dequeue() {
  for (;;) {
    int s;
//...
    // wait for client to connect
    struct sockaddr addr;
    socklen_t len = sizeof(addr);
    RESTARTABLE(::accept(listener(), &addr, &len), s);
//...
    // peer credential look okay so we read the request
    LinuxAttachOperation* op = read_request(s);

この辺か.
 
この関数はどこで呼ばれているか.

//hotspot/src/share/vm/services/attachListener.cpp:350
static void attach_listener_thread_entry(JavaThread* thread, TRAPS) {
//...
  for (;;) {
    AttachOperation* op = AttachListener::dequeue();

//hotspot/src/share/vm/services/attachListener.cpp:401

// Starts the Attach Listener thread
void AttachListener::init() {
//...
  { MutexLocker mu(Threads_lock);
    JavaThread* listener_thread = new JavaThread(&attach_listener_thread_entry);
//...
    Threads::add(listener_thread);
    Thread::start(listener_thread);

なるほど.このAttachListenerを生成するときに既に別スレッドを生成して,乗っけているのか.
 

AttachListenerの生成タイミング

jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
//...
  // Start Attach Listener if +StartAttachListener or it can't be started lazily
  if (!DisableAttachMechanism) {
//...
      AttachListener::init();
//...
  }

あぁ,VMを作られる時には必ず生成されるのか.
 

まとめの関数のコール図

ちょっとわかりにくくなったので関数のコール図を作成

Threads::create_vm (VM生成時)
       |
AttachListener::init (VM)
       |
new JavaThread(&attach_listener_thread_entry)
       |
       |=======================> attach_listener_thread_entry
       |                                    |
       |                                    |<================|
       |                                    |                 |
       |                                    |                 |
       |                         AttachListener::dequeue      |
       |                                    |                 |
       |                                    |                 |
       |                                    |                 |
       |                         (socket exist?)---------------
       |                                    |
       |                         accept(Unixドメインのソケット通信を待つ)
       |                                    
signal_thread_entry
(SIGQUITが送られた場合,次へ)
       |
AttachListener::is_init_trigger
(.attach_pidがある場合,次へ)
       |
LinuxAttachListener::init
(Unixドメインソケット生成)