読者です 読者をやめる 読者になる 読者になる

6.AttachAPIへのコマンド - jconsoleの仕組み

ソケット作成

まずは,どこでソケットを作っているか.
LinuxVirtualMachine.javaの見てみる.

//jdk/src/solaris/classes/sun/tools/attach/LinuxVirtualMachine.java:141
    InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {
        assert args.length <= 3;                // includes null
//...
        // create UNIX socket
        int s = socket();

socket()で作成しているらしい.というかコメントが多くて助かるな.読みやすい.
 

JINIを使ったソケット生成

どうやらsocket()はCで書いていて,それをJINIで呼び出しているっぽい.

//jdk/src/solaris/classes/sun/tools/attach/LinuxVirtualMachine.java:331
    static native int socket() throws IOException;
//...
    static {
        System.loadLibrary("attach");
        isLinuxThreads = isLinuxThreads();
    }

 

Cライブラリではどうやっているか

ルール的にnativeのディレクトリにCのコードはありそうだ.

//jdk/src/solaris/native/sun/tools/attach/LinuxVirtualMachine.c:130
JNIEXPORT jint JNICALL Java_sun_tools_attach_LinuxVirtualMachine_socket
  (JNIEnv *env, jclass cls)
{
    int fd = socket(PF_UNIX, SOCK_STREAM, 0);
    if (fd == -1) {
        JNU_ThrowIOExceptionWithLastError(env, "socket");
    }
    return (jint)fd;
}

Unixドメインソケットを作りファイルディスクプリタを返している.
 

エージェントを送る

さて,ではエージェントはどうやって送っていたか.

//jdk/src/share/classes/sun/tools/attach/HotSpotVirtualMachine.java:1238819355
    private void loadAgentLibrary(String agentLibrary, boolean isAbsolute, String options)
        throws AgentLoadException, AgentInitializationException, IOException
    {
        InputStream in = execute("load",
                                 agentLibrary,
                                 isAbsolute ? "true" : "false",
                                 options);

executeメソッドは先ほどのsocket()を作っていた部分であった.
 

//jdk/src/solaris/classes/sun/tools/attach/LinuxVirtualMachine.java:169
        // connected - write request
        // <ver> <cmd> <args...>
        try {
            writeString(s, PROTOCOL_VERSION);
            writeString(s, cmd);

            for (int i=0; i<3; i++) {
                if (i < args.length && args[i] != null) {
                    writeString(s, (String)args[i]);
                } else {
                    writeString(s, "");
                }
            }
<プロトコルのバージョン> <コマンド名> <引数...>

という形式でソケット通信.さて受け取る側ではどうなっていたか.
 

VM側でのコマンド起動

VMでacceptしていた部分を見る.

//hotspot/src/share/vm/services/attachListener.cpp:349
static void attach_listener_thread_entry(JavaThread* thread, TRAPS) {
//...
      // 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;
        }
      }
//...:327
// names must be of length <= AttachOperation::name_length_max
static AttachOperationFunctionInfo funcs[] = {
  { "agentProperties", 	get_agent_properties },
  { "datadump",         data_dump },
#ifndef SERVICES_KERNEL
  { "dumpheap",         dump_heap },
#endif  // SERVICES_KERNEL
  { "load",             JvmtiExport::load_agent_library },
  { "properties",       get_system_properties },
  { "threaddump",	thread_dump },
  { "inspectheap",      heap_inspection },
  { "setflag",          set_flag },
  { "printflag",        print_flag },
  { NULL, 		NULL }
};

コマンド名から関数テーブルのfuncsを引いて,関数を取り出している.
 

//hotspot/src/share/vm/services/attachListener.cpp:381
      // 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);

取得した関数を呼び出し,その結果を引数にAttachLisnerのcomplete関数呼び出し.
 

//hotspot/src/os/linux/vm/attachListener_linux.cpp:379
// Complete an operation by sending the operation result and any result
// output to the client. At this time the socket is in blocking mode so
// potentially we can block if there is a lot of data and the client is
// non-responsive. For most operations this is a non-issue because the
// default send buffer is sufficient to buffer everything. In the future
// if there are operations that involves a very big reply then it the
// socket could be made non-blocking and a timeout could be used.

void LinuxAttachOperation::complete(jint result, bufferedStream* st) {
//...
  // write operation result
  char msg[32];
  sprintf(msg, "%d\n", result);
  int rc = LinuxAttachListener::write_fully(this->socket(), msg, strlen(msg));

  // write any result data 
  if (rc == 0) {
    LinuxAttachListener::write_fully(this->socket(), (char*) st->base(), st->size());
    ::shutdown(this->socket(), 2);
  }
//...

次のacceptに備えて,shutdownでクライアントへ切断を伝える.
 

まとめのフロー図

LinuxVirtualMachine::execute
          |
socket(make socket)
          |
LinuxVirtualMachine::loadAgentLibrary =====================> attach_listener_thread_entry
(   sending)                          |
          |                                                           |
          |                                                  { "load", JvmtiExport::load_agent_library },
          |                                                           |
          |                                                  JvmtiExport::load_agent_library
          |                                                           |
          |<================================================ LinuxAttachOperation::complete