JVM Attach機(jī)制實現(xiàn)

Attach是什么

在講這個之前绢慢,我們先來點大家都知道的東西,當(dāng)我們感覺線程一直卡在某個地方洛波,想知道卡在哪里胰舆,首先想到的是進(jìn)行線程dump,而常用的命令是jstack 奋岁,我們就可以看到如下線程棧了

2014-06-18 12:56:14
Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.51-b03 mixed mode):

"Attach Listener" daemon prio=5 tid=0x00007fb0c6800800 nid=0x440b waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Service Thread" daemon prio=5 tid=0x00007fb0c584d800 nid=0x5303 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" daemon prio=5 tid=0x00007fb0c482e000 nid=0x5103 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" daemon prio=5 tid=0x00007fb0c482c800 nid=0x4f03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=5 tid=0x00007fb0c4815800 nid=0x4d03 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=5 tid=0x00007fb0c4813800 nid=0x3903 in Object.wait() [0x00000001187d2000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000007aaa85568> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
    - locked <0x00000007aaa85568> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:189)

"Reference Handler" daemon prio=5 tid=0x00007fb0c4800000 nid=0x3703 in Object.wait() [0x00000001186cf000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000007aaa850f0> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:503)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
    - locked <0x00000007aaa850f0> (a java.lang.ref.Reference$Lock)

"main" prio=5 tid=0x00007fb0c5800800 nid=0x1903 waiting on condition [0x0000000107962000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at Test.main(Test.java:5)

"VM Thread" prio=5 tid=0x00007fb0c583d800 nid=0x3503 runnable

"GC task thread#0 (ParallelGC)" prio=5 tid=0x00007fb0c401e000 nid=0x2503 runnable

"GC task thread#1 (ParallelGC)" prio=5 tid=0x00007fb0c401e800 nid=0x2703 runnable

"GC task thread#2 (ParallelGC)" prio=5 tid=0x00007fb0c401f800 nid=0x2903 runnable

"GC task thread#3 (ParallelGC)" prio=5 tid=0x00007fb0c4020000 nid=0x2b03 runnable

"GC task thread#4 (ParallelGC)" prio=5 tid=0x00007fb0c4020800 nid=0x2d03 runnable

"GC task thread#5 (ParallelGC)" prio=5 tid=0x00007fb0c4021000 nid=0x2f03 runnable

"GC task thread#6 (ParallelGC)" prio=5 tid=0x00007fb0c4022000 nid=0x3103 runnable

"GC task thread#7 (ParallelGC)" prio=5 tid=0x00007fb0c4022800 nid=0x3303 runnable

"VM Periodic Task Thread" prio=5 tid=0x00007fb0c5845000 nid=0x5503 waiting on condition

大家是否注意過上面圈起來的兩個線程思瘟,”Attach Listener”和“Signal Dispatcher”,這兩個線程是我們這次要講的Attach機(jī)制的關(guān)鍵闻伶,先偷偷告訴各位滨攻,其實Attach Listener這個線程在jvm起來的時候可能并沒有的,后面會細(xì)說。
??那Attach機(jī)制是什么光绕?說簡單點就是jvm提供一種jvm進(jìn)程間通信的能力女嘲,能讓一個進(jìn)程傳命令給另外一個進(jìn)程,并讓它執(zhí)行內(nèi)部的一些操作诞帐,比如說我們?yōu)榱俗屃硗庖粋€jvm進(jìn)程把線程dump出來欣尼,那么我們跑了一個jstack的進(jìn)程,然后傳了個pid的參數(shù)停蕉,告訴它要哪個進(jìn)程進(jìn)行線程dump愕鼓,既然是兩個進(jìn)程,那肯定涉及到進(jìn)程間通信慧起,以及傳輸協(xié)議的定義菇晃,比如要執(zhí)行什么操作,傳了什么參數(shù)等
Attach能做些什么
??總結(jié)起來說蚓挤,比如內(nèi)存dump磺送,線程dump,類信息統(tǒng)計(比如加載的類及大小以及實例個數(shù)等)灿意,動態(tài)加載agent(使用過btrace的應(yīng)該不陌生),動態(tài)設(shè)置vm flag(但是并不是所有的flag都可以設(shè)置的缤剧,因為有些flag是在jvm啟動過程中使用的馅袁,是一次性的),打印vm flag鞭执,獲取系統(tǒng)屬性等司顿,這些對應(yīng)的源碼(AttachListener.cpp)如下

static AttachOperationFunctionInfo funcs[] = {
  { "agentProperties",  get_agent_properties },
  { "datadump",         data_dump },
  { "dumpheap",         dump_heap },
  { "load",             JvmtiExport::load_agent_library },
  { "properties",       get_system_properties },
  { "threaddump",       thread_dump },
  { "inspectheap",      heap_inspection },
  { "setflag",          set_flag },
  { "printflag",        print_flag },
  { "jcmd",             jcmd },
  { NULL,               NULL }
};

后面是命令對應(yīng)的處理函數(shù)。
Attach在jvm里如何實現(xiàn)的
Attach Listener線程的創(chuàng)建
??前面也提到了兄纺,jvm在啟動過程中可能并沒有啟動Attach Listener這個線程大溜,可以通過jvm參數(shù)來啟動,代碼 (Threads::create_vm)如下:

 if (!DisableAttachMechanism) {
   if (StartAttachListener || AttachListener::init_at_startup()) {
     AttachListener::init();
   }
 }
bool AttachListener::init_at_startup() {
 if (ReduceSignalUsage) {
   return true;
 } else {
   return false;
 }
}
``
??其中DisableAttachMechanism估脆,StartAttachListener 钦奋,ReduceSignalUsage均默認(rèn)是false(globals.hpp)
product(bool, DisableAttachMechanism, false,                              \
        "Disable mechanism that allows tools to Attach to this VM”)   
product(bool, StartAttachListener, false,                                 \
         "Always start Attach Listener at VM startup")  
product(bool, ReduceSignalUsage, false,                                   \
         "Reduce the use of OS signals in Java and/or the VM”)  
??因此AttachListener::init()并不會被執(zhí)行,而Attach Listener線程正是在此方法里創(chuàng)建的
// Starts the Attach Listener thread
void AttachListener::init() {
 EXCEPTION_MARK;
 klassOop k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK);
 instanceKlassHandle klass (THREAD, k);
 instanceHandle thread_oop = klass->allocate_instance_handle(CHECK);

 const char thread_name[] = "Attach Listener";
 Handle string = java_lang_String::create_from_str(thread_name, CHECK);

 // Initialize thread_oop to put it into the system threadGroup
 Handle thread_group (THREAD, Universe::system_thread_group());
 JavaValue result(T_VOID);
 JavaCalls::call_special(&result, thread_oop,
                      klass,
                      vmSymbols::object_initializer_name(),
                      vmSymbols::threadgroup_string_void_signature(),
                      thread_group,
                      string,
                      CHECK);

 KlassHandle group(THREAD, SystemDictionary::ThreadGroup_klass());
 JavaCalls::call_special(&result,
                       thread_group,
                       group,
                       vmSymbols::add_method_name(),
                       vmSymbols::thread_void_signature(),
                       thread_oop,             // ARG 1
                       CHECK);

 { MutexLocker mu(Threads_lock);
   JavaThread* listener_thread = new JavaThread(&Attach_listener_thread_entry);

   // Check that thread and osthread were created
   if (listener_thread == NULL || listener_thread->osthread() == NULL) {
     vm_exit_during_initialization("java.lang.OutOfMemoryError",
                                   "unable to create new native thread");
   }

   java_lang_Thread::set_thread(thread_oop(), listener_thread);
   java_lang_Thread::set_daemon(thread_oop());

   listener_thread->set_threadObj(thread_oop());
   Threads::add(listener_thread);
   Thread::start(listener_thread);
 }
}

既然在啟動的時候不會創(chuàng)建這個線程疙赠,那么我們在上面看到的那個線程是怎么創(chuàng)建的呢付材,這個就要關(guān)注另外一個線程“Signal Dispatcher”了,顧名思義是處理信號的圃阳,這個線程是在jvm啟動的時候就會創(chuàng)建的厌衔,具體代碼就不說了。
??下面以jstack的實現(xiàn)來說明觸發(fā)Attach這一機(jī)制進(jìn)行的過程捍岳,jstack命令的實現(xiàn)其實是一個叫做JStack.java的類富寿,查看jstack代碼后會走到下面的方法里

private static void runThreadDump(String pid, String args[]) throws Exception {
        VirtualMachine vm = null;
        try {
            vm = VirtualMachine.Attach(pid);
        } catch (Exception x) {
            String msg = x.getMessage();
            if (msg != null) {
                System.err.println(pid + ": " + msg);
            } else {
                x.printStackTrace();
            }
            if ((x instanceof AttachNotSupportedException) &&
                (loadSAClass() != null)) {
                System.err.println("The -F option can be used when the target " +
                    "process is not responding");
            }
            System.exit(1);
        }

        // Cast to HotSpotVirtualMachine as this is implementation specific
        // method.
        InputStream in = ((HotSpotVirtualMachine)vm).remoteDataDump((Object[])args);

        // read to EOF and just print output
        byte b[] = new byte[256];
        int n;
        do {
            n = in.read(b);
            if (n > 0) {
                String s = new String(b, 0, n, "UTF-8");
                System.out.print(s);
            }
        } while (n > 0);
        in.close();
        vm.detach();
    }

請注意VirtualMachine.Attach(pid);這行代碼睬隶,觸發(fā)Attach pid的關(guān)鍵,如果是在linux下會走到下面的構(gòu)造函數(shù)

LinuxVirtualMachine(AttachProvider provider, String vmid)
        throws AttachNotSupportedException, IOException
    {
        super(provider, vmid);

        // This provider only understands pids
        int pid;
        try {
            pid = Integer.parseInt(vmid);
        } catch (NumberFormatException x) {
            throw new AttachNotSupportedException("Invalid process identifier");
        }

        // 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).
                if (isLinuxThreads) {
                    int mpid;
                    try {
                        mpid = getLinuxThreadsManager(pid);
                    } catch (IOException x) {
                        throw new AttachNotSupportedException(x.getMessage());
                    }
                    assert(mpid >= 1);
                    sendQuitToChildrenOf(mpid);
                } else {
                    sendQuitTo(pid);
                }

                // give the target VM time to start the Attach mechanism
                int i = 0;
                long delay = 200;
                int retries = (int)(AttachTimeout() / delay);
                do {
                    try {
                        Thread.sleep(delay);
                    } catch (InterruptedException x) { }
                    path = findSocketFile(pid);
                    i++;
                } while (i <= retries && path == null);
                if (path == null) {
                    throw new AttachNotSupportedException(
                        "Unable to open socket file: target process not responding " +
                        "or HotSpot VM not loaded");
                }
            } finally {
                f.delete();
            }
        }

        // Check that the file owner/permission to avoid Attaching to
        // bogus process
        checkPermissions(path);

        // Check that we can connect to the process
        // - this ensures we throw the permission denied error now rather than
        // later when we attempt to enqueue a command.
        int s = socket();
        try {
            connect(s, path);
        } finally {
            close(s);
        }
    }

這里要解釋下代碼了页徐,首先看到調(diào)用了createAttachFile方法在目標(biāo)進(jìn)程的cwd目錄下創(chuàng)建了一個文件/proc//cwd/.Attach_pid苏潜,這個在后面的信號處理過程中會取出來做判斷(為了安全),另外我們知道在linux下線程是用進(jìn)程實現(xiàn)的变勇,在jvm啟動過程中會創(chuàng)建很多線程恤左,比如我們上面的信號線程,也就是會看到很多的pid(應(yīng)該是LWP)搀绣,那么如何找到這個信號處理線程呢飞袋,從上面實現(xiàn)來看是找到我們傳進(jìn)去的pid的父進(jìn)程,然后給它的所有子進(jìn)程都發(fā)送一個SIGQUIT信號豌熄,而jvm里除了信號線程授嘀,其他線程都設(shè)置了對此信號的屏蔽物咳,因此收不到該信號锣险,于是該信號就傳給了“Signal Dispatcher”,在傳完之后作輪詢等待看目標(biāo)進(jìn)程是否創(chuàng)建了某個文件览闰,AttachTimeout默認(rèn)超時時間是5000ms芯肤,可通過設(shè)置系統(tǒng)變量sun.tools.Attach.AttachTimeout來指定,下面是Signal Dispatcher線程的entry實現(xiàn)

static void signal_thread_entry(JavaThread* thread, TRAPS) {
  os::set_priority(thread, NearMaxPriority);
  while (true) {
    int sig;
    {
      // FIXME : Currently we have not decieded what should be the status
      //         for this java thread blocked here. Once we decide about
      //         that we should fix this.
      sig = os::signal_wait();
    }
    if (sig == os::sigexitnum_pd()) {
       // Terminate the signal thread
       return;
    }

    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;
        }
        // Print stack traces
        // Any SIGBREAK operations added here should make sure to flush
        // the output stream (e.g. tty->flush()) after output.  See 4803766.
        // Each module also prints an extra carriage return after its output.
        VM_PrintThreads op;
        VMThread::execute(&op);
        VM_PrintJNI jni_op;
        VMThread::execute(&jni_op);
        VM_FindDeadlocks op1(tty);
        VMThread::execute(&op1);
        Universe::print_heap_at_SIGBREAK();
        if (PrintClassHistogram) {
          VM_GC_HeapInspection op1(gclog_or_tty, true /* force full GC before heap inspection */,
                                   true /* need_prologue */);
          VMThread::execute(&op1);
        }
        if (JvmtiExport::should_post_data_dump()) {
          JvmtiExport::post_data_dump();
        }
        break;
      }
      ….
      }
    }
  }
}

當(dāng)信號是SIGBREAK(在jvm里做了#define压鉴,其實就是SIGQUIT)的時候崖咨,就會觸發(fā)

AttachListener::is_init_trigger()的執(zhí)行,
bool AttachListener::is_init_trigger() {
  if (init_at_startup() || is_initialized()) {
    return false;               // initialized at startup or already initialized
  }
  char fn[PATH_MAX+1];
  sprintf(fn, ".Attach_pid%d", os::current_process_id());
  int ret;
  struct stat64 st;
  RESTARTABLE(::stat64(fn, &st), ret);
  if (ret == -1) {
    snprintf(fn, sizeof(fn), "%s/.Attach_pid%d",
             os::get_temp_directory(), 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;
}

一開始會判斷當(dāng)前進(jìn)程目錄下是否有個.Attach_pid文件(前面提到了)油吭,如果沒有就會在/tmp下創(chuàng)建一個/tmp/.Attach_pid击蹲,當(dāng)那個文件的uid和自己的uid是一致的情況下(為了安全)再調(diào)用init方法

// Starts the Attach Listener thread
void AttachListener::init() {
  EXCEPTION_MARK;
  klassOop k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK);
  instanceKlassHandle klass (THREAD, k);
  instanceHandle thread_oop = klass->allocate_instance_handle(CHECK);

  const char thread_name[] = "Attach Listener";
  Handle string = java_lang_String::create_from_str(thread_name, CHECK);

  // Initialize thread_oop to put it into the system threadGroup
  Handle thread_group (THREAD, Universe::system_thread_group());
  JavaValue result(T_VOID);
  JavaCalls::call_special(&result, thread_oop,
                       klass,
                       vmSymbols::object_initializer_name(),
                       vmSymbols::threadgroup_string_void_signature(),
                       thread_group,
                       string,
                       CHECK);

  KlassHandle group(THREAD, SystemDictionary::ThreadGroup_klass());
  JavaCalls::call_special(&result,
                        thread_group,
                        group,
                        vmSymbols::add_method_name(),
                        vmSymbols::thread_void_signature(),
                        thread_oop,             // ARG 1
                        CHECK);

  { MutexLocker mu(Threads_lock);
    JavaThread* listener_thread = new JavaThread(&Attach_listener_thread_entry);

    // Check that thread and osthread were created
    if (listener_thread == NULL || listener_thread->osthread() == NULL) {
      vm_exit_during_initialization("java.lang.OutOfMemoryError",
                                    "unable to create new native thread");
    }

    java_lang_Thread::set_thread(thread_oop(), listener_thread);
    java_lang_Thread::set_daemon(thread_oop());

    listener_thread->set_threadObj(thread_oop());
    Threads::add(listener_thread);
    Thread::start(listener_thread);
  }
}

此時水落石出了,看到創(chuàng)建了一個線程婉宰,并且取名為Attach Listener歌豺。再看看其子類LinuxAttachListener的init方法

int LinuxAttachListener::init() {
  char path[UNIX_PATH_MAX];          // socket file
  char initial_path[UNIX_PATH_MAX];  // socket file during setup
  int listener;                      // listener socket (file descriptor)

  // register function to cleanup
  ::atexit(listener_cleanup);

  int n = snprintf(path, UNIX_PATH_MAX, "%s/.java_pid%d",
                   os::get_temp_directory(), os::current_process_id());
  if (n < (int)UNIX_PATH_MAX) {
    n = snprintf(initial_path, UNIX_PATH_MAX, "%s.tmp", path);
  }
  if (n >= (int)UNIX_PATH_MAX) {
    return -1;
  }

  // create the listener socket
  listener = ::socket(PF_UNIX, SOCK_STREAM, 0);
  if (listener == -1) {
    return -1;
  }

  // bind socket
  struct sockaddr_un addr;
  addr.sun_family = AF_UNIX;
  strcpy(addr.sun_path, initial_path);
  ::unlink(initial_path);
  int res = ::bind(listener, (struct sockaddr*)&addr, sizeof(addr));
  if (res == -1) {
    RESTARTABLE(::close(listener), res);
    return -1;
  }

  // put in listen mode, set permissions, and rename into place
  res = ::listen(listener, 5);
  if (res == 0) {
      RESTARTABLE(::chmod(initial_path, S_IREAD|S_IWRITE), res);
      if (res == 0) {
          res = ::rename(initial_path, path);
      }
  }
  if (res == -1) {
    RESTARTABLE(::close(listener), res);
    ::unlink(initial_path);
    return -1;
  }
  set_path(path);
  set_listener(listener);

  return 0;
}

看到其創(chuàng)建了一個監(jiān)聽套接字,并創(chuàng)建了一個文件/tmp/.java_pid心包,這個文件就是客戶端之前一直在輪詢等待的文件类咧,隨著這個文件的生成,意味著Attach的過程圓滿結(jié)束了蟹腾。
Attach listener接收請求
??看看它的entry實現(xiàn)Attach_listener_thread_entry

static void Attach_listener_thread_entry(JavaThread* thread, TRAPS) {
  os::set_priority(thread, NearMaxPriority);

  thread->record_stack_base_and_size();

  if (AttachListener::pd_init() != 0) {
    return;
  }
  AttachListener::set_initialized();

  for (;;) {
    AttachOperation* op = AttachListener::dequeue();
    if (op == NULL) {
      return;   // dequeue failed or shutdown
    }

    ResourceMark rm;
    bufferedStream st;
    jint res = JNI_OK;

    // handle special detachall operation
    if (strcmp(op->name(), AttachOperation::detachall_operation_name()) == 0) {
      AttachListener::detachall();
    } else {
      // find the function to dispatch too
      AttachOperationFunctionInfo* info = NULL;
      for (int i=0; funcs[i].name != NULL; i++) {
        const char* name = funcs[i].name;
        assert(strlen(name) <= AttachOperation::name_length_max, "operation <= name_length_max");
        if (strcmp(op->name(), name) == 0) {
          info = &(funcs[i]);
          break;
        }
      }

      // check for platform dependent Attach operation
      if (info == NULL) {
        info = AttachListener::pd_find_operation(op->name());
      }

      if (info != NULL) {
        // dispatch to the function that implements this operation
        res = (info->func)(op, &st);
      } else {
        st.print("Operation %s not recognized!", op->name());
        res = JNI_ERR;
      }
    }

    // operation complete - send result and output to client
    op->complete(res, &st);
  }
}

從代碼來看就是從隊列里不斷取AttachOperation痕惋,然后找到請求命令對應(yīng)的方法進(jìn)行執(zhí)行,比如我們一開始說的jstack命令娃殖,找到 { “threaddump”, thread_dump }的映射關(guān)系值戳,然后執(zhí)行thread_dump方法
??再來看看其要調(diào)用的AttachListener::dequeue(),

AttachOperation* AttachListener::dequeue() {
  JavaThread* thread = JavaThread::current();
  ThreadBlockInVM tbivm(thread);

  thread->set_suspend_equivalent();
  // cleared by handle_special_suspend_equivalent_condition() or
  // java_suspend_self() via check_and_wait_while_suspended()

  AttachOperation* op = LinuxAttachListener::dequeue();

  // were we externally suspended while we were waiting?
  thread->check_and_wait_while_suspended();

  return op;
}

最終調(diào)用的是LinuxAttachListener::dequeue()炉爆,

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);
    if (s == -1) {
      return NULL;      // log a warning?
    }

    // get the credentials of the peer and check the effective uid/guid
    // - check with jeff on this.
    struct ucred cred_info;
    socklen_t optlen = sizeof(cred_info);
    if (::getsockopt(s, SOL_SOCKET, SO_PEERCRED, (void*)&cred_info, &optlen) == -1) {
      int res;
      RESTARTABLE(::close(s), res);
      continue;
    }
    uid_t euid = geteuid();
    gid_t egid = getegid();

    if (cred_info.uid != euid || cred_info.gid != egid) {
      int res;
      RESTARTABLE(::close(s), res);
      continue;
    }

    // peer credential look okay so we read the request
    LinuxAttachOperation* op = read_request(s);
    if (op == NULL) {
      int res;
      RESTARTABLE(::close(s), res);
      continue;
    } else {
      return op;
    }
  }
}

我們看到如果沒有請求的話堕虹,會一直accept在那里柿隙,當(dāng)來了請求,然后就會創(chuàng)建一個套接字鲫凶,并讀取數(shù)據(jù)禀崖,構(gòu)建出LinuxAttachOperation返回并執(zhí)行。
??整個過程就這樣了螟炫,從Attach線程創(chuàng)建到接收請求波附,處理請求。
文章轉(zhuǎn)自:http://lovestblog.cn/blog/2014/06/18/jvm-attach/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昼钻,一起剝皮案震驚了整個濱河市掸屡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌然评,老刑警劉巖仅财,帶你破解...
    沈念sama閱讀 223,207評論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碗淌,居然都是意外死亡盏求,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評論 3 400
  • 文/潘曉璐 我一進(jìn)店門亿眠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碎罚,“玉大人,你說我怎么就攤上這事纳像【A遥” “怎么了?”我有些...
    開封第一講書人閱讀 170,031評論 0 366
  • 文/不壞的土叔 我叫張陵竟趾,是天一觀的道長憔购。 經(jīng)常有香客問我,道長岔帽,這世上最難降的妖魔是什么玫鸟? 我笑而不...
    開封第一講書人閱讀 60,334評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮山卦,結(jié)果婚禮上鞋邑,老公的妹妹穿的比我還像新娘。我一直安慰自己账蓉,他們只是感情好枚碗,可當(dāng)我...
    茶點故事閱讀 69,322評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著铸本,像睡著了一般肮雨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上箱玷,一...
    開封第一講書人閱讀 52,895評論 1 314
  • 那天怨规,我揣著相機(jī)與錄音陌宿,去河邊找鬼。 笑死波丰,一個胖子當(dāng)著我的面吹牛壳坪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掰烟,決...
    沈念sama閱讀 41,300評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼爽蝴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了纫骑?” 一聲冷哼從身側(cè)響起蝎亚,我...
    開封第一講書人閱讀 40,264評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎先馆,沒想到半個月后发框,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,784評論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煤墙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,870評論 3 343
  • 正文 我和宋清朗相戀三年梅惯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片番捂。...
    茶點故事閱讀 40,989評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡个唧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出设预,到底是詐尸還是另有隱情,我是刑警寧澤犁河,帶...
    沈念sama閱讀 36,649評論 5 351
  • 正文 年R本政府宣布鳖枕,位于F島的核電站,受9級特大地震影響桨螺,放射性物質(zhì)發(fā)生泄漏宾符。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,331評論 3 336
  • 文/蒙蒙 一灭翔、第九天 我趴在偏房一處隱蔽的房頂上張望魏烫。 院中可真熱鬧,春花似錦肝箱、人聲如沸哄褒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,814評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呐赡。三九已至,卻和暖如春骏融,著一層夾襖步出監(jiān)牢的瞬間链嘀,已是汗流浹背萌狂。 一陣腳步聲響...
    開封第一講書人閱讀 33,940評論 1 275
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留怀泊,地道東北人茫藏。 一個月前我還...
    沈念sama閱讀 49,452評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像霹琼,于是被迫代替她去往敵國和親刷允。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,995評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 姓名:郭金 學(xué)號:17101223407 【嵌牛導(dǎo)讀】:之前看到一個用jstack查看死鎖的例子碧囊∈髟睿總結(jié)了一下:js...
    寶寶啦啦啦閱讀 800評論 0 0
  • 從JDK6開始引入,除了Solaris平臺的Sun JVM支持遠(yuǎn)程的Attach糯而,在其他平臺都只允許Attach到...
    andersonoy閱讀 3,446評論 0 3
  • Java 應(yīng)用性能優(yōu)化是一個老生常談的話題天通,典型的性能問題如頁面響應(yīng)慢、接口超時熄驼,服務(wù)器負(fù)載高像寒、并發(fā)數(shù)低,數(shù)據(jù)庫頻...
    Rick617閱讀 7,343評論 1 9
  • 參數(shù)設(shè)置 在Java虛擬機(jī)的參數(shù)中瓜贾,有3種表示方法用“ps -ef |grep "java"命令诺祸,可以得到當(dāng)前Ja...
    九問閱讀 9,155評論 2 52
  • 烏龍明月是誰,很多人不認(rèn)識她祭芦,但都認(rèn)識李笑來筷笨。烏龍明月是李笑來老師的得意門生,演講課龟劲、寫作課胃夏、編程課,無一不是學(xué)霸...
    教在美國閱讀 766評論 5 5