JVM Attach实现原理剖析
本文转载自JVM Attach实现原理剖析
前言
本文旨在从理论上分析JVM 在 Linux 环境下 Attach 操作的前因后果,以及 JVM 为此而设计并实现的解决方案,通过本文,我希望能够讲述清楚如下三个主要方面的内容。
Attach 为什么而出现
Attach的出现究其根本原因,应该就是为了实现 Java 进程(A)与进程(B)之间的本地通信。一旦这个通信通道能够成功建立,那么进程 A 就能通知进程 B 去执行某些操作,从而达到监控进程 B 或者控制进程 B 的某些行为的目的。如 jstack、jmap等 JDK 自带的工具,基本都是通过 Attach 机制去达成各自想要的目的的。至于 jstack、jmap 能做什么、怎么做,就不再本文的讨论范围了,请自行百度或者 Google。
Attach 在 JVM 底层实现的根本原理是什么
Attach 实现的根本原理就是使用了 Linux 下是文件 Socket 通信(详情可以自行百度或 Google)。有人也许会问,为什么要采用文件 socket 而不采用网络 socket?我个人认为也许一方面是为了效率(避免了网络协议的解析、数据包的封装和解封装等),另一方面是为了减少对系统资源的占用(如网络端口占用)。采用文件 socket 通信,就好比两个进程通过事先约定好的协议,对同一个文件进行读写操作,以达到信息的交互和共享。简单理解成如下图所示的模型
通过/tmp/.java.pid2345这个文件,实现客户进程与目标进程2345的通信。
Attach 在 JVM 中实现的源码分析
源码的分析主要分三阶段进行,这里要达到的目的是,弄 Attach 的清楚来龙去脉,本文的所有源码都是基于 Open JDK 1.8的,大家可以自行去下载 Open JDK 1.8 的源码。
目标JVM 对OS信号监听的实现
或许你会想,在最开始的时候,目标 JVM 是怎么知道有某个进程想 attach 它自己的?答案很简单,就是目标 JVM 在启动的时候,在 JVM 内部启动了一个监听线程,这个线程的名字叫“Signal Dispatcher”,该线程的作用是,监听并处理 OS 的信号。至于什么是 OS 的信号(可以自行百度或 Google),简单理解就是,Linux系统允许进程与进程之间通过过信号的方式进行通信,如触发某个操作(操作由接受到信号的进程自定义)。如平常我们用的最多的就是 kill -9 ${pid}来杀死某个进程,kill进程通过向${pid}的进程发送一个编号为“9”号的信号,来通知系统强制结束${pid}的生命周期。
接下来我们就通过源码截图的方式来呈现一下“Signal Dispatcher”线程的创建过程。
首先进入 JVM 的启动类:/jdk/src/share/bin/main.c
1 int
2 main(int argc, char **argv)
3 {
4 int margc;
5 char** margv;
6 const jboolean const_javaw = JNI_FALSE;
7 #endif /* JAVAW */
8 #ifdef _WIN32
9 {
10 int i = 0;
11 if (getenv(JLDEBUG_ENV_ENTRY) != NULL) {
12 printf("Windows original main args:\n");
13 for (i = 0 ; i < __argc ; i++) {
14 printf("wwwd_args[%d] = %s\n", i, __argv[i]);
15 }
16 }
17 }
18 JLI_CmdToArgs(GetCommandLine());
19 margc = JLI_GetStdArgc();
20 // add one more to mark the end
21 margv = (char **)JLI_MemAlloc((margc + 1) * (sizeof(char *)));
22 {
23 int i = 0;
24 StdArg *stdargs = JLI_GetStdArgs();
25 for (i = 0 ; i < margc ; i++) {
26 margv[i] = stdargs[i].arg;
27 }
28 margv[i] = NULL;
29 }
30 #else /* *NIXES */
31 margc = argc;
32 margv = argv;
33 #endif /* WIN32 */
34 return JLI_Launch(margc, margv,
35 sizeof(const_jargs) / sizeof(char *), const_jargs,
36 sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
37 FULL_VERSION,
38 DOT_VERSION,
39 (const_progname != NULL) ? const_progname : *margv,
40 (const_launcher != NULL) ? const_launcher : *margv,
41 (const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
42 const_cpwildcard, const_javaw, const_ergo_class);
43 }
这个类里边最重要的一个方法就是最后的JLI_Launch,这个方法的实现存在于jdk/src/share/bin/java.c 中(大家应该都不陌生平时我们运行 java 程序时,都是采用 java com.***.Main来启动的吧)。
1 /*
2 * Entry point.
3 */
4 int
5 JLI_Launch(int argc, char ** argv, /* main argc, argc */
6 int jargc, const char** jargv, /* java args */
7 int appclassc, const char** appclassv, /* app classpath */
8 const char* fullversion, /* full version defined */
9 const char* dotversion, /* dot version defined */
10 const char* pname, /* program name */
11 const char* lname, /* launcher name */
12 jboolean javaargs, /* JAVA_ARGS */
13 jboolean cpwildcard, /* classpath wildcard*/
14 jboolean javaw, /* windows-only javaw */
15 jint ergo /* ergonomics class policy */
16 )
17 {
18 int mode = LM_UNKNOWN;
19 char *what = NULL;
20 char *cpath = 0;
21 char *main_class = NULL;
22 int ret;
23 InvocationFunctions ifn;
24 jlong start, end;
25 char jvmpath[MAXPATHLEN];
26 char jrepath[MAXPATHLEN];
27 char jvmcfg[MAXPATHLEN];
28
29 _fVersion = fullversion;
30 _dVersion = dotversion;
31 _launcher_name = lname;
32 _program_name = pname;
33 _is_java_args = javaargs;
34 _wc_enabled = cpwildcard;
35 _ergo_policy = ergo;
36
37 InitLauncher(javaw);
38 DumpState();
39 if (JLI_IsTraceLauncher()) {
40 int i;
41 printf("Command line args:\n");
42 for (i = 0; i < argc ; i++) {
43 printf("argv[%d] = %s\n", i, argv[i]);
44 }
45 AddOption("-Dsun.java.launcher.diag=true", NULL);
46 }
47
48 /*
49 * Make sure the specified version of the JRE is running.
50 *
51 * There are three things to note about the SelectVersion() routine:
52 * 1) If the version running isn't correct, this routine doesn't
53 * return (either the correct version has been exec'd or an error
54 * was issued).
55 * 2) Argc and Argv in this scope are *not* altered by this routine.
56 * It is the responsibility of subsequent code to ignore the
57 * arguments handled by this routine.
58 * 3) As a side-effect, the variable "main_class" is guaranteed to
59 * be set (if it should ever be set). This isn't exactly the
60 * poster child for structured programming, but it is a small
61 * price to pay for not processing a jar file operand twice.
62 * (Note: This side effect has been disabled. See comment on
63 * bugid 5030265 below.)
64 */
65 SelectVersion(argc, argv, &main_class);
66
67 CreateExecutionEnvironment(&argc, &argv,
68 jrepath, sizeof(jrepath),
69 jvmpath, sizeof(jvmpath),
70 jvmcfg, sizeof(jvmcfg));
71
72 ifn.CreateJavaVM = 0;
73 ifn.GetDefaultJavaVMInitArgs = 0;
74
75 if (JLI_IsTraceLauncher()) {
76 start = CounterGet();
77 }
78
79 if (!LoadJavaVM(jvmpath, &ifn)) {
80 return(6);
81 }
82
83 if (JLI_IsTraceLauncher()) {
84 end = CounterGet();
85 }
86
87 JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",
88 (long)(jint)Counter2Micros(end-start));
89
90 ++argv;
91 --argc;
92
93 if (IsJavaArgs()) {
94 /* Preprocess wrapper arguments */
95 TranslateApplicationArgs(jargc, jargv, &argc, &argv);
96 if (!AddApplicationOptions(appclassc, appclassv)) {
97 return(1);
98 }
99 } else {
100 /* Set default CLASSPATH */
101 cpath = getenv("CLASSPATH");
102 if (cpath == NULL) {
103 cpath = ".";
104 }
105 SetClassPath(cpath);
106 }
107
108 /* Parse command line options; if the return value of
109 * ParseArguments is false, the program should exit.
110 */
111 if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
112 {
113 return(ret);
114 }
115
116 /* Override class path if -jar flag was specified */
117 if (mode == LM_JAR) {
118 SetClassPath(what); /* Override class path */
119 }
120
121 /* set the -Dsun.java.command pseudo property */
122 SetJavaCommandLineProp(what, argc, argv);
123
124 /* Set the -Dsun.java.launcher pseudo property */
125 SetJavaLauncherProp();
126
127 /* set the -Dsun.java.launcher.* platform properties */
128 SetJavaLauncherPlatformProps();
129
130 return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
131 }
这个方法中,进行了一系列必要的操作,如libjvm.so的加载、参数解析、Classpath 的获取和设置、系统属性的设置、JVM 初始化等等,不过和本文相关的主要是130行的 JVMInit 方法,接下来我们看下这个方法的实现(位于/jdk/src/solaris/bin/java_md_solinux.c)。
1 int
2 JVMInit(InvocationFunctions* ifn, jlong threadStackSize,
3 int argc, char **argv,
4 int mode, char *what, int ret)
5 {
6 ShowSplashScreen();
7 return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
8 }
这里请关注两个点,ContinueInNewThread方法 和 ifn 入参。ContinueInNewThread位于 java.c中,而 ifn 则携带了libjvm.so中的几个非常重要的函数(CreateJavaVM/GetDefaultJavaVMInitArgs/GetCreatedJavaVMs),这里我们重点关注CreateJavaVM
1 int
2 ContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,
3 int argc, char **argv,
4 int mode, char *what, int ret)
5 {
6
7 /*
8 * If user doesn't specify stack size, check if VM has a preference.
9 * Note that HotSpot no longer supports JNI_VERSION_1_1 but it will
10 * return its default stack size through the init args structure.
11 */
12 if (threadStackSize == 0) {
13 struct JDK1_1InitArgs args1_1;
14 memset((void*)&args1_1, 0, sizeof(args1_1));
15 args1_1.version = JNI_VERSION_1_1;
16 ifn->GetDefaultJavaVMInitArgs(&args1_1); /* ignore return value */
17 if (args1_1.javaStackSize > 0) {
18 threadStackSize = args1_1.javaStackSize;
19 }
20 }
21
22 { /* Create a new thread to create JVM and invoke main method */
23 JavaMainArgs args;
24 int rslt;
25
26 args.argc = argc;
27 args.argv = argv;
28 args.mode = mode;
29 args.what = what;
30 args.ifn = *ifn;
31
32 rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);
33 /* If the caller has deemed there is an error we
34 * simply return that, otherwise we return the value of
35 * the callee
36 */
37 return (ret != 0) ? ret : rslt;
38 }
39 }
可以看出,这里进行了 JavaMainArgs 参数设置,设置完成之后,在32行处调用了 ContinueInNewThread0 (位于/jdk/src/solaris/bin/java_md_solinux.c)方法,该方法中传入了 JavaMain 函数指针和 args 参数,这二者至关重要。接下来看下其源码
1 /*
2 * Block current thread and continue execution in a new thread
3 */
4 int
5 ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {
6 int rslt;
7 #ifdef __linux__
8 pthread_t tid;
9 pthread_attr_t attr;
10 pthread_attr_init(&attr);
11 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
12
13 if (stack_size > 0) {
14 pthread_attr_setstacksize(&attr, stack_size);
15 }
16
17 if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
18 void * tmp;
19 pthread_join(tid, &tmp);
20 rslt = (int)tmp;
21 } else {
22 /*
23 * Continue execution in current thread if for some reason (e.g. out of
24 * memory/LWP) a new thread can't be created. This will likely fail
25 * later in continuation as JNI_CreateJavaVM needs to create quite a
26 * few new threads, anyway, just give it a try..
27 */
28 rslt = continuation(args);
29 }
30
31 pthread_attr_destroy(&attr);
32 #else /* ! __linux__ */
33 thread_t tid;
34 long flags = 0;
35 if (thr_create(NULL, stack_size, (void *(*)(void *))continuation, args, flags, &tid) == 0) {
36 void * tmp;
37 thr_join(tid, NULL, &tmp);
38 rslt = (int)tmp;
39 } else {
40 /* See above. Continue in current thread if thr_create() failed */
41 rslt = continuation(args);
42 }
43 #endif /* __linux__ */
44 return rslt;
45 }
这里最关键的点在于,如果是 linux 环境下,则创建了一个 pthread_t 的线程来运行传入的 JavaMain 函数,并且将 args 参数也一并传入了。这时候,我们唯一要关注的便是 JavaMain (在jdk/src/share/bin/java.c )函数,请看源码
1 int JNICALL
2 JavaMain(void * _args)
3 {
4 JavaMainArgs *args = (JavaMainArgs *)_args;
5 int argc = args->argc;
6 char **argv = args->argv;
7 int mode = args->mode;
8 char *what = args->what;
9 InvocationFunctions ifn = args->ifn;
10
11 JavaVM *vm = 0;
12 JNIEnv *env = 0;
13 jclass mainClass = NULL;
14 jclass appClass = NULL; // actual application class being launched
15 jmethodID mainID;
16 jobjectArray mainArgs;
17 int ret = 0;
18 jlong start, end;
19
20 RegisterThread();
21
22 /* Initialize the virtual machine */
23 start = CounterGet();
24 if (!InitializeJVM(&vm, &env, &ifn)) {
25 JLI_ReportErrorMessage(JVM_ERROR1);
26 exit(1);
27 }
28
29 if (showSettings != NULL) {
30 ShowSettings(env, showSettings);
31 CHECK_EXCEPTION_LEAVE(1);
32 }
33
34 if (printVersion || showVersion) {
35 PrintJavaVersion(env, showVersion);
36 CHECK_EXCEPTION_LEAVE(0);
37 if (printVersion) {
38 LEAVE();
39 }
40 }
41
42 /* If the user specified neither a class name nor a JAR file */
43 if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
44 PrintUsage(env, printXUsage);
45 CHECK_EXCEPTION_LEAVE(1);
46 LEAVE();
47 }
48
49 FreeKnownVMs(); /* after last possible PrintUsage() */
50
51 if (JLI_IsTraceLauncher()) {
52 end = CounterGet();
53 JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
54 (long)(jint)Counter2Micros(end-start));
55 }
56
57 /* At this stage, argc/argv have the application's arguments */
58 if (JLI_IsTraceLauncher()){
59 int i;
60 printf("%s is '%s'\n", launchModeNames[mode], what);
61 printf("App's argc is %d\n", argc);
62 for (i=0; i < argc; i++) {
63 printf(" argv[%2d] = '%s'\n", i, argv[i]);
64 }
65 }
66
67 ret = 1;
68
69 /*
70 * Get the application's main class.
71 *
72 * See bugid 5030265. The Main-Class name has already been parsed
73 * from the manifest, but not parsed properly for UTF-8 support.
74 * Hence the code here ignores the value previously extracted and
75 * uses the pre-existing code to reextract the value. This is
76 * possibly an end of release cycle expedient. However, it has
77 * also been discovered that passing some character sets through
78 * the environment has "strange" behavior on some variants of
79 * Windows. Hence, maybe the manifest parsing code local to the
80 * launcher should never be enhanced.
81 *
82 * Hence, future work should either:
83 * 1) Correct the local parsing code and verify that the
84 * Main-Class attribute gets properly passed through
85 * all environments,
86 * 2) Remove the vestages of maintaining main_class through
87 * the environment (and remove these comments).
88 *
89 * This method also correctly handles launching existing JavaFX
90 * applications that may or may not have a Main-Class manifest entry.
91 */
92 mainClass = LoadMainClass(env, mode, what);
93 CHECK_EXCEPTION_NULL_LEAVE(mainClass);
94 /*
95 * In some cases when launching an application that needs a helper, e.g., a
96 * JavaFX application with no main method, the mainClass will not be the
97 * applications own main class but rather a helper class. To keep things
98 * consistent in the UI we need to track and report the application main class.
99 */
100 appClass = GetApplicationClass(env);
101 NULL_CHECK_RETURN_VALUE(appClass, -1);
102 /*
103 * PostJVMInit uses the class name as the application name for GUI purposes,
104 * for example, on OSX this sets the application name in the menu bar for
105 * both SWT and JavaFX. So we'll pass the actual application class here
106 * instead of mainClass as that may be a launcher or helper class instead
107 * of the application class.
108 */
109 PostJVMInit(env, appClass, vm);
110 /*
111 * The LoadMainClass not only loads the main class, it will also ensure
112 * that the main method's signature is correct, therefore further checking
113 * is not required. The main method is invoked here so that extraneous java
114 * stacks are not in the application stack trace.
115 */
116 mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
117 "([Ljava/lang/String;)V");
118 CHECK_EXCEPTION_NULL_LEAVE(mainID);
119
120 /* Build platform specific argument array */
121 mainArgs = CreateApplicationArgs(env, argv, argc);
122 CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
123
124 /* Invoke main method. */
125 (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
126
127 /*
128 * The launcher's exit code (in the absence of calls to
129 * System.exit) will be non-zero if main threw an exception.
130 */
131 ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
132 LEAVE();
133 }
和本小节相关的函数为InitializeJVM函数,在这个函数中,调用CreateJavaVM方法,这个方法就是之前在加载 libjvm.so 的时候,从动态库中获取的,首先看InitializeJVM的源码
1 /*
2 * Initializes the Java Virtual Machine. Also frees options array when
3 * finished.
4 */
5 static jboolean
6 InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn)
7 {
8 JavaVMInitArgs args;
9 jint r;
10
11 memset(&args, 0, sizeof(args));
12 args.version = JNI_VERSION_1_2;
13 args.nOptions = numOptions;
14 args.options = options;
15 args.ignoreUnrecognized = JNI_FALSE;
16
17 if (JLI_IsTraceLauncher()) {
18 int i = 0;
19 printf("JavaVM args:\n ");
20 printf("version 0x%08lx, ", (long)args.version);
21 printf("ignoreUnrecognized is %s, ",
22 args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE");
23 printf("nOptions is %ld\n", (long)args.nOptions);
24 for (i = 0; i < numOptions; i++)
25 printf(" option[%2d] = '%s'\n",
26 i, args.options[i].optionString);
27 }
28
29 r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
30 JLI_MemFree(options);
31 return r == JNI_OK;
32 }
29行处,调用 CreateJavaVM(定义在hotspot/src/share/vm/prims/jni.cpp) 方法,来进行 JVM 虚拟机的真正创建过程,源码如下
1 _JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
2 #ifndef USDT2
3 HS_DTRACE_PROBE3(hotspot_jni, CreateJavaVM__entry, vm, penv, args);
4 #else /* USDT2 */
5 HOTSPOT_JNI_CREATEJAVAVM_ENTRY(
6 (void **) vm, penv, args);
7 #endif /* USDT2 */
8
9 jint result = JNI_ERR;
10 DT_RETURN_MARK(CreateJavaVM, jint, (const jint&)result);
11
12 // We're about to use Atomic::xchg for synchronization. Some Zero
13 // platforms use the GCC builtin __sync_lock_test_and_set for this,
14 // but __sync_lock_test_and_set is not guaranteed to do what we want
15 // on all architectures. So we check it works before relying on it.
16 #if defined(ZERO) && defined(ASSERT)
17 {
18 jint a = 0xcafebabe;
19 jint b = Atomic::xchg(0xdeadbeef, &a);
20 void *c = &a;
21 void *d = Atomic::xchg_ptr(&b, &c);
22 assert(a == (jint) 0xdeadbeef && b == (jint) 0xcafebabe, "Atomic::xchg() works");
23 assert(c == &b && d == &a, "Atomic::xchg_ptr() works");
24 }
25 #endif // ZERO && ASSERT
26
27 // At the moment it's only possible to have one Java VM,
28 // since some of the runtime state is in global variables.
29
30 // We cannot use our mutex locks here, since they only work on
31 // Threads. We do an atomic compare and exchange to ensure only
32 // one thread can call this method at a time
33
34 // We use Atomic::xchg rather than Atomic::add/dec since on some platforms
35 // the add/dec implementations are dependent on whether we are running
36 // on a multiprocessor, and at this stage of initialization the os::is_MP
37 // function used to determine this will always return false. Atomic::xchg
38 // does not have this problem.
39 if (Atomic::xchg(1, &vm_created) == 1) {
40 return JNI_EEXIST; // already created, or create attempt in progress
41 }
42 if (Atomic::xchg(0, &safe_to_recreate_vm) == 0) {
43 return JNI_ERR; // someone tried and failed and retry not allowed.
44 }
45
46 assert(vm_created == 1, "vm_created is true during the creation");
47
48 /**
49 * Certain errors during initialization are recoverable and do not
50 * prevent this method from being called again at a later time
51 * (perhaps with different arguments). However, at a certain
52 * point during initialization if an error occurs we cannot allow
53 * this function to be called again (or it will crash). In those
54 * situations, the 'canTryAgain' flag is set to false, which atomically
55 * sets safe_to_recreate_vm to 1, such that any new call to
56 * JNI_CreateJavaVM will immediately fail using the above logic.
57 */
58 bool can_try_again = true;
59
60 result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
61 if (result == JNI_OK) {
62 JavaThread *thread = JavaThread::current();
63 /* thread is thread_in_vm here */
64 *vm = (JavaVM *)(&main_vm);
65 *(JNIEnv**)penv = thread->jni_environment();
66
67 // Tracks the time application was running before GC
68 RuntimeService::record_application_start();
69
70 // Notify JVMTI
71 if (JvmtiExport::should_post_thread_life()) {
72 JvmtiExport::post_thread_start(thread);
73 }
74
75 EventThreadStart event;
76 if (event.should_commit()) {
77 event.set_javalangthread(java_lang_Thread::thread_id(thread->threadObj()));
78 event.commit();
79 }
80
81 #ifndef PRODUCT
82 #ifndef TARGET_OS_FAMILY_windows
83 #define CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(f) f()
84 #endif
85
86 // Check if we should compile all classes on bootclasspath
87 if (CompileTheWorld) ClassLoader::compile_the_world();
88 if (ReplayCompiles) ciReplay::replay(thread);
89
90 // Some platforms (like Win*) need a wrapper around these test
91 // functions in order to properly handle error conditions.
92 CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(test_error_handler);
93 CALL_TEST_FUNC_WITH_WRAPPER_IF_NEEDED(execute_internal_vm_tests);
94 #endif
95
96 // Since this is not a JVM_ENTRY we have to set the thread state manually before leaving.
97 ThreadStateTransition::transition_and_fence(thread, _thread_in_vm, _thread_in_native);
98 } else {
99 if (can_try_again) {
100 // reset safe_to_recreate_vm to 1 so that retrial would be possible
101 safe_to_recreate_vm = 1;
102 }
103
104 // Creation failed. We must reset vm_created
105 *vm = 0;
106 *(JNIEnv**)penv = 0;
107 // reset vm_created last to avoid race condition. Use OrderAccess to
108 // control both compiler and architectural-based reordering.
109 OrderAccess::release_store(&vm_created, 0);
110 }
111
112 return result;
113 }
这里只关注最核心的方法是60行的Threads::create_vm(hotspot/src/share/vm/runtime/Thread.cpp) 方法,在这个方法中,进行了大量的初始化操作,不过,这里我们只关注其中的一个点,就是 os::signal_init() 方法的调用,这就是启动“Signal Dispatcher”线程的地方。先看 create_vm 的源码
1 jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
2
3 extern void JDK_Version_init();
4
5 // Check version
6 if (!is_supported_jni_version(args->version)) return JNI_EVERSION;
7
8 // Initialize the output stream module
9 ostream_init();
10
11 // Process java launcher properties.
12 Arguments::process_sun_java_launcher_properties(args);
13
14 // Initialize the os module before using TLS
15 os::init();
16
17 // Initialize system properties.
18 Arguments::init_system_properties();
19
20 // So that JDK version can be used as a discrimintor when parsing arguments
21 JDK_Version_init();
22
23 // Update/Initialize System properties after JDK version number is known
24 Arguments::init_version_specific_system_properties();
25
26 // Parse arguments
27 jint parse_result = Arguments::parse(args);
28 if (parse_result != JNI_OK) return parse_result;
29
30 os::init_before_ergo();
31
32 jint ergo_result = Arguments::apply_ergo();
33 if (ergo_result != JNI_OK) return ergo_result;
34
35 if (PauseAtStartup) {
36 os::pause();
37 }
38
39 #ifndef USDT2
40 HS_DTRACE_PROBE(hotspot, vm__init__begin);
41 #else /* USDT2 */
42 HOTSPOT_VM_INIT_BEGIN();
43 #endif /* USDT2 */
44
45 // Record VM creation timing statistics
46 TraceVmCreationTime create_vm_timer;
47 create_vm_timer.start();
48
49 // Timing (must come after argument parsing)
50 TraceTime timer("Create VM", TraceStartupTime);
51
52 // Initialize the os module after parsing the args
53 jint os_init_2_result = os::init_2();
54 if (os_init_2_result != JNI_OK) return os_init_2_result;
55
56 jint adjust_after_os_result = Arguments::adjust_after_os();
57 if (adjust_after_os_result != JNI_OK) return adjust_after_os_result;
58
59 // intialize TLS
60 ThreadLocalStorage::init();
61
62 // Bootstrap native memory tracking, so it can start recording memory
63 // activities before worker thread is started. This is the first phase
64 // of bootstrapping, VM is currently running in single-thread mode.
65 MemTracker::bootstrap_single_thread();
66
67 // Initialize output stream logging
68 ostream_init_log();
69
70 // Convert -Xrun to -agentlib: if there is no JVM_OnLoad
71 // Must be before create_vm_init_agents()
72 if (Arguments::init_libraries_at_startup()) {
73 convert_vm_init_libraries_to_agents();
74 }
75
76 // Launch -agentlib/-agentpath and converted -Xrun agents
77 if (Arguments::init_agents_at_startup()) {
78 create_vm_init_agents();
79 }
80
81 // Initialize Threads state
82 _thread_list = NULL;
83 _number_of_threads = 0;
84 _number_of_non_daemon_threads = 0;
85
86 // Initialize global data structures and create system classes in heap
87 vm_init_globals();
88
89 // Attach the main thread to this os thread
90 JavaThread* main_thread = new JavaThread();
91 main_thread->set_thread_state(_thread_in_vm);
92 // must do this before set_active_handles and initialize_thread_local_storage
93 // Note: on solaris initialize_thread_local_storage() will (indirectly)
94 // change the stack size recorded here to one based on the java thread
95 // stacksize. This adjusted size is what is used to figure the placement
96 // of the guard pages.
97 main_thread->record_stack_base_and_size();
98 main_thread->initialize_thread_local_storage();
99
100 main_thread->set_active_handles(JNIHandleBlock::allocate_block());
101
102 if (!main_thread->set_as_starting_thread()) {
103 vm_shutdown_during_initialization(
104 "Failed necessary internal allocation. Out of swap space");
105 delete main_thread;
106 *canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
107 return JNI_ENOMEM;
108 }
109
110 // Enable guard page *after* os::create_main_thread(), otherwise it would
111 // crash Linux VM, see notes in os_linux.cpp.
112 main_thread->create_stack_guard_pages();
113
114 // Initialize Java-Level synchronization subsystem
115 ObjectMonitor::Initialize() ;
116
117 // Second phase of bootstrapping, VM is about entering multi-thread mode
118 MemTracker::bootstrap_multi_thread();
119
120 // Initialize global modules
121 jint status = init_globals();
122 if (status != JNI_OK) {
123 delete main_thread;
124 *canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
125 return status;
126 }
127
128 // Should be done after the heap is fully created
129 main_thread->cache_global_variables();
130
131 HandleMark hm;
132
133 { MutexLocker mu(Threads_lock);
134 Threads::add(main_thread);
135 }
136
137 // Any JVMTI raw monitors entered in onload will transition into
138 // real raw monitor. VM is setup enough here for raw monitor enter.
139 JvmtiExport::transition_pending_onload_raw_monitors();
140
141 // Fully start NMT
142 MemTracker::start();
143
144 // Create the VMThread
145 { TraceTime timer("Start VMThread", TraceStartupTime);
146 VMThread::create();
147 Thread* vmthread = VMThread::vm_thread();
148
149 if (!os::create_thread(vmthread, os::vm_thread))
150 vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");
151
152 // Wait for the VM thread to become ready, and VMThread::run to initialize
153 // Monitors can have spurious returns, must always check another state flag
154 {
155 MutexLocker ml(Notify_lock);
156 os::start_thread(vmthread);
157 while (vmthread->active_handles() == NULL) {
158 Notify_lock->wait();
159 }
160 }
161 }
162
163 assert (Universe::is_fully_initialized(), "not initialized");
164 if (VerifyDuringStartup) {
165 // Make sure we're starting with a clean slate.
166 VM_Verify verify_op;
167 VMThread::execute(&verify_op);
168 }
169
170 EXCEPTION_MARK;
171
172 // At this point, the Universe is initialized, but we have not executed
173 // any byte code. Now is a good time (the only time) to dump out the
174 // internal state of the JVM for sharing.
175 if (DumpSharedSpaces) {
176 MetaspaceShared::preload_and_dump(CHECK_0);
177 ShouldNotReachHere();
178 }
179
180 // Always call even when there are not JVMTI environments yet, since environments
181 // may be attached late and JVMTI must track phases of VM execution
182 JvmtiExport::enter_start_phase();
183
184 // Notify JVMTI agents that VM has started (JNI is up) - nop if no agents.
185 JvmtiExport::post_vm_start();
186
187 {
188 TraceTime timer("Initialize java.lang classes", TraceStartupTime);
189
190 if (EagerXrunInit && Arguments::init_libraries_at_startup()) {
191 create_vm_init_libraries();
192 }
193
194 initialize_class(vmSymbols::java_lang_String(), CHECK_0);
195
196 // Initialize java_lang.System (needed before creating the thread)
197 initialize_class(vmSymbols::java_lang_System(), CHECK_0);
198 initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK_0);
199 Handle thread_group = create_initial_thread_group(CHECK_0);
200 Universe::set_main_thread_group(thread_group());
201 initialize_class(vmSymbols::java_lang_Thread(), CHECK_0);
202 oop thread_object = create_initial_thread(thread_group, main_thread, CHECK_0);
203 main_thread->set_threadObj(thread_object);
204 // Set thread status to running since main thread has
205 // been started and running.
206 java_lang_Thread::set_thread_status(thread_object,
207 java_lang_Thread::RUNNABLE);
208
209 // The VM creates & returns objects of this class. Make sure it's initialized.
210 initialize_class(vmSymbols::java_lang_Class(), CHECK_0);
211
212 // The VM preresolves methods to these classes. Make sure that they get initialized
213 initialize_class(vmSymbols::java_lang_reflect_Method(), CHECK_0);
214 initialize_class(vmSymbols::java_lang_ref_Finalizer(), CHECK_0);
215 call_initializeSystemClass(CHECK_0);
216
217 // get the Java runtime name after java.lang.System is initialized
218 JDK_Version::set_runtime_name(get_java_runtime_name(THREAD));
219 JDK_Version::set_runtime_version(get_java_runtime_version(THREAD));
220
221 // an instance of OutOfMemory exception has been allocated earlier
222 initialize_class(vmSymbols::java_lang_OutOfMemoryError(), CHECK_0);
223 initialize_class(vmSymbols::java_lang_NullPointerException(), CHECK_0);
224 initialize_class(vmSymbols::java_lang_ClassCastException(), CHECK_0);
225 initialize_class(vmSymbols::java_lang_ArrayStoreException(), CHECK_0);
226 initialize_class(vmSymbols::java_lang_ArithmeticException(), CHECK_0);
227 initialize_class(vmSymbols::java_lang_StackOverflowError(), CHECK_0);
228 initialize_class(vmSymbols::java_lang_IllegalMonitorStateException(), CHECK_0);
229 initialize_class(vmSymbols::java_lang_IllegalArgumentException(), CHECK_0);
230 }
231
232 // See : bugid 4211085.
233 // Background : the static initializer of java.lang.Compiler tries to read
234 // property"java.compiler" and read & write property "java.vm.info".
235 // When a security manager is installed through the command line
236 // option "-Djava.security.manager", the above properties are not
237 // readable and the static initializer for java.lang.Compiler fails
238 // resulting in a NoClassDefFoundError. This can happen in any
239 // user code which calls methods in java.lang.Compiler.
240 // Hack : the hack is to pre-load and initialize this class, so that only
241 // system domains are on the stack when the properties are read.
242 // Currently even the AWT code has calls to methods in java.lang.Compiler.
243 // On the classic VM, java.lang.Compiler is loaded very early to load the JIT.
244 // Future Fix : the best fix is to grant everyone permissions to read "java.compiler" and
245 // read and write"java.vm.info" in the default policy file. See bugid 4211383
246 // Once that is done, we should remove this hack.
247 initialize_class(vmSymbols::java_lang_Compiler(), CHECK_0);
248
249 // More hackery - the static initializer of java.lang.Compiler adds the string "nojit" to
250 // the java.vm.info property if no jit gets loaded through java.lang.Compiler (the hotspot
251 // compiler does not get loaded through java.lang.Compiler). "java -version" with the
252 // hotspot vm says "nojit" all the time which is confusing. So, we reset it here.
253 // This should also be taken out as soon as 4211383 gets fixed.
254 reset_vm_info_property(CHECK_0);
255
256 quicken_jni_functions();
257
258 // Must be run after init_ft which initializes ft_enabled
259 if (TRACE_INITIALIZE() != JNI_OK) {
260 vm_exit_during_initialization("Failed to initialize tracing backend");
261 }
262
263 // Set flag that basic initialization has completed. Used by exceptions and various
264 // debug stuff, that does not work until all basic classes have been initialized.
265 set_init_completed();
266
267 #ifndef USDT2
268 HS_DTRACE_PROBE(hotspot, vm__init__end);
269 #else /* USDT2 */
270 HOTSPOT_VM_INIT_END();
271 #endif /* USDT2 */
272
273 // record VM initialization completion time
274 #if INCLUDE_MANAGEMENT
275 Management::record_vm_init_completed();
276 #endif // INCLUDE_MANAGEMENT
277
278 // Compute system loader. Note that this has to occur after set_init_completed, since
279 // valid exceptions may be thrown in the process.
280 // Note that we do not use CHECK_0 here since we are inside an EXCEPTION_MARK and
281 // set_init_completed has just been called, causing exceptions not to be shortcut
282 // anymore. We call vm_exit_during_initialization directly instead.
283 SystemDictionary::compute_java_system_loader(THREAD);
284 if (HAS_PENDING_EXCEPTION) {
285 vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION));
286 }
287
288 #if INCLUDE_ALL_GCS
289 // Support for ConcurrentMarkSweep. This should be cleaned up
290 // and better encapsulated. The ugly nested if test would go away
291 // once things are properly refactored. XXX YSR
292 if (UseConcMarkSweepGC || UseG1GC) {
293 if (UseConcMarkSweepGC) {
294 ConcurrentMarkSweepThread::makeSurrogateLockerThread(THREAD);
295 } else {
296 ConcurrentMarkThread::makeSurrogateLockerThread(THREAD);
297 }
298 if (HAS_PENDING_EXCEPTION) {
299 vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION));
300 }
301 }
302 #endif // INCLUDE_ALL_GCS
303
304 // Always call even when there are not JVMTI environments yet, since environments
305 // may be attached late and JVMTI must track phases of VM execution
306 JvmtiExport::enter_live_phase();
307
308 // Signal Dispatcher needs to be started before VMInit event is posted
309 os::signal_init();
310
311 // Start Attach Listener if +StartAttachListener or it can't be started lazily
312 if (!DisableAttachMechanism) {
313 AttachListener::vm_start();
314 if (StartAttachListener || AttachListener::init_at_startup()) {
315 AttachListener::init();
316 }
317 }
318
319 // Launch -Xrun agents
320 // Must be done in the JVMTI live phase so that for backward compatibility the JDWP
321 // back-end can launch with -Xdebug -Xrunjdwp.
322 if (!EagerXrunInit && Arguments::init_libraries_at_startup()) {
323 create_vm_init_libraries();
324 }
325
326 // Notify JVMTI agents that VM initialization is complete - nop if no agents.
327 JvmtiExport::post_vm_initialized();
328
329 if (TRACE_START() != JNI_OK) {
330 vm_exit_during_initialization("Failed to start tracing backend.");
331 }
332
333 if (CleanChunkPoolAsync) {
334 Chunk::start_chunk_pool_cleaner_task();
335 }
336
337 // initialize compiler(s)
338 #if defined(COMPILER1) || defined(COMPILER2) || defined(SHARK)
339 CompileBroker::compilation_init();
340 #endif
341
342 if (EnableInvokeDynamic) {
343 // Pre-initialize some JSR292 core classes to avoid deadlock during class loading.
344 // It is done after compilers are initialized, because otherwise compilations of
345 // signature polymorphic MH intrinsics can be missed
346 // (see SystemDictionary::find_method_handle_intrinsic).
347 initialize_class(vmSymbols::java_lang_invoke_MethodHandle(), CHECK_0);
348 initialize_class(vmSymbols::java_lang_invoke_MemberName(), CHECK_0);
349 initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK_0);
350 }
351
352 #if INCLUDE_MANAGEMENT
353 Management::initialize(THREAD);
354 #endif // INCLUDE_MANAGEMENT
355
356 if (HAS_PENDING_EXCEPTION) {
357 // management agent fails to start possibly due to
358 // configuration problem and is responsible for printing
359 // stack trace if appropriate. Simply exit VM.
360 vm_exit(1);
361 }
362
363 if (Arguments::has_profile()) FlatProfiler::engage(main_thread, true);
364 if (MemProfiling) MemProfiler::engage();
365 StatSampler::engage();
366 if (CheckJNICalls) JniPeriodicChecker::engage();
367
368 BiasedLocking::init();
369
370 if (JDK_Version::current().post_vm_init_hook_enabled()) {
371 call_postVMInitHook(THREAD);
372 // The Java side of PostVMInitHook.run must deal with all
373 // exceptions and provide means of diagnosis.
374 if (HAS_PENDING_EXCEPTION) {
375 CLEAR_PENDING_EXCEPTION;
376 }
377 }
378
379 {
380 MutexLockerEx ml(PeriodicTask_lock, Mutex::_no_safepoint_check_flag);
381 // Make sure the watcher thread can be started by WatcherThread::start()
382 // or by dynamic enrollment.
383 WatcherThread::make_startable();
384 // Start up the WatcherThread if there are any periodic tasks
385 // NOTE: All PeriodicTasks should be registered by now. If they
386 // aren't, late joiners might appear to start slowly (we might
387 // take a while to process their first tick).
388 if (PeriodicTask::num_tasks() > 0) {
389 WatcherThread::start();
390 }
391 }
392
393 // Give os specific code one last chance to start
394 os::init_3();
395
396 create_vm_timer.end();
397 #ifdef ASSERT
398 _vm_complete = true;
399 #endif
400 return JNI_OK;
401 }
309 行处,看到了os::signal_init() 的调用(hotspot/src/share/vm/runtime/os.cpp),这就是我们要找的。接着,我们看下其具体实现
1 void os::signal_init() {
2 if (!ReduceSignalUsage) {
3 // Setup JavaThread for processing signals
4 EXCEPTION_MARK;
5 Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK);
6 instanceKlassHandle klass (THREAD, k);
7 instanceHandle thread_oop = klass->allocate_instance_handle(CHECK);
8
9 const char thread_name[] = "Signal Dispatcher";
10 Handle string = java_lang_String::create_from_str(thread_name, CHECK);
11
12 // Initialize thread_oop to put it into the system threadGroup
13 Handle thread_group (THREAD, Universe::system_thread_group());
14 JavaValue result(T_VOID);
15 JavaCalls::call_special(&result, thread_oop,
16 klass,
17 vmSymbols::object_initializer_name(),
18 vmSymbols::threadgroup_string_void_signature(),
19 thread_group,
20 string,
21 CHECK);
22
23 KlassHandle group(THREAD, SystemDictionary::ThreadGroup_klass());
24 JavaCalls::call_special(&result,
25 thread_group,
26 group,
27 vmSymbols::add_method_name(),
28 vmSymbols::thread_void_signature(),
29 thread_oop, // ARG 1
30 CHECK);
31
32 os::signal_init_pd();
33
34 { MutexLocker mu(Threads_lock);
35 JavaThread* signal_thread = new JavaThread(&signal_thread_entry);
36
37 // At this point it may be possible that no osthread was created for the
38 // JavaThread due to lack of memory. We would have to throw an exception
39 // in that case. However, since this must work and we do not allow
40 // exceptions anyway, check and abort if this fails.
41 if (signal_thread == NULL || signal_thread->osthread() == NULL) {
42 vm_exit_during_initialization("java.lang.OutOfMemoryError",
43 "unable to create new native thread");
44 }
45
46 java_lang_Thread::set_thread(thread_oop(), signal_thread);
47 java_lang_Thread::set_priority(thread_oop(), NearMaxPriority);
48 java_lang_Thread::set_daemon(thread_oop());
49
50 signal_thread->set_threadObj(thread_oop());
51 Threads::add(signal_thread);
52 Thread::start(signal_thread);
53 }
54 // Handle ^BREAK
55 os::signal(SIGBREAK, os::user_handler());
56 }
57 }
这里的完全可以看出来,在此函数中35行处,创建了一个 java 线程,用于执行signal_thread_entry 函数,那我们来看看,这个 signal_thread_entry 函数到底做了什么?
1 // sigexitnum_pd is a platform-specific special signal used for terminating the Signal thread.
2 static void signal_thread_entry(JavaThread* thread, TRAPS) {
3 os::set_priority(thread, NearMaxPriority);
4 while (true) {
5 int sig;
6 {
7 // FIXME : Currently we have not decieded what should be the status
8 // for this java thread blocked here. Once we decide about
9 // that we should fix this.
10 sig = os::signal_wait();
11 }
12 if (sig == os::sigexitnum_pd()) {
13 // Terminate the signal thread
14 return;
15 }
16
17 switch (sig) {
18 case SIGBREAK: {
19 // Check if the signal is a trigger to start the Attach Listener - in that
20 // case don't print stack traces.
21 if (!DisableAttachMechanism && AttachListener::is_init_trigger()) {
22 continue;
23 }
24 // Print stack traces
25 // Any SIGBREAK operations added here should make sure to flush
26 // the output stream (e.g. tty->flush()) after output. See 4803766.
27 // Each module also prints an extra carriage return after its output.
28 VM_PrintThreads op;
29 VMThread::execute(&op);
30 VM_PrintJNI jni_op;
31 VMThread::execute(&jni_op);
32 VM_FindDeadlocks op1(tty);
33 VMThread::execute(&op1);
34 Universe::print_heap_at_SIGBREAK();
35 if (PrintClassHistogram) {
36 VM_GC_HeapInspection op1(gclog_or_tty, true /* force full GC before heap inspection */);
37 VMThread::execute(&op1);
38 }
39 if (JvmtiExport::should_post_data_dump()) {
40 JvmtiExport::post_data_dump();
41 }
42 break;
43 }
44 default: {
45 // Dispatch the signal to java
46 HandleMark hm(THREAD);
47 Klass* k = SystemDictionary::resolve_or_null(vmSymbols::sun_misc_Signal(), THREAD);
48 KlassHandle klass (THREAD, k);
49 if (klass.not_null()) {
50 JavaValue result(T_VOID);
51 JavaCallArguments args;
52 args.push_int(sig);
53 JavaCalls::call_static(
54 &result,
55 klass,
56 vmSymbols::dispatch_name(),
57 vmSymbols::int_void_signature(),
58 &args,
59 THREAD
60 );
61 }
62 if (HAS_PENDING_EXCEPTION) {
63 // tty is initialized early so we don't expect it to be null, but
64 // if it is we can't risk doing an initialization that might
65 // trigger additional out-of-memory conditions
66 if (tty != NULL) {
67 char klass_name[256];
68 char tmp_sig_name[16];
69 const char* sig_name = "UNKNOWN";
70 InstanceKlass::cast(PENDING_EXCEPTION->klass())->
71 name()->as_klass_external_name(klass_name, 256);
72 if (os::exception_name(sig, tmp_sig_name, 16) != NULL)
73 sig_name = tmp_sig_name;
74 warning("Exception %s occurred dispatching signal %s to handler"
75 "- the VM may need to be forcibly terminated",
76 klass_name, sig_name );
77 }
78 CLEAR_PENDING_EXCEPTION;
79 }
80 }
81 }
82 }
83 }
函数里面意思已经很清晰明了了,首先在10行处,有一个os::signal_wait()的调用,该调用的主要是阻塞当前线程,并等待接收系统信号,然后再根据接收到的信号 sig 做 switch 逻辑,对于不同的信号做不同的处理。至此,关于“目标 JVM 对OS信号监听的实现”这一点,就已经分析结束了。简单的一句话总结就是,JVM 在启动的时候,会创建一个名为“Signal Dispatcher”的线程用于接收os 的信号,以便对不同信号分别做处理。
文件 Socket 通信的通道的创建
经过3.1的分析,我们已经知道在 JVM 启动之后,内部会有线程监听并处理 os 的信号,那么,这个时候,如果我们想和已经启动的 JVM 建立通信,当然就可以毫不犹豫的使用信号来进行了。不过,基于信号的通信,也是存在限制的,一方面,os 支持的信号是有限的,二来信号的通信往往是单向的,不方便通信双方进行高效的通信。基于这些,笔者认为,为了使得 Client JVM 和 Target JVM 更好的通信,就采用了 Socket 通信来实现二者的通信。那接下来我们看看,这个通道究竟是如何创建的?
当我们需要 attach 到某个目标 JVM 进程上去的时候,我们通常会写如下代码
1 VirtualMachine vm = VirtualMachine.attach(pid);
这样我们就能得到目标 JVM 的相关信息了,是不是很简单?不过,今天我们要做的可不是这么简单的事情,我们需要深入其后,了解其根本。接下来我们就以com.sun.tools.attach.VirtualMachine的 attach 方法入手,逐层揭开其神秘面纱。
1 public static VirtualMachine attach(String id)
2 throws AttachNotSupportedException, IOException
3 {
4 if (id == null) {
5 throw new NullPointerException("id cannot be null");
6 }
7 List<AttachProvider> providers = AttachProvider.providers();
8 if (providers.size() == 0) {
9 throw new AttachNotSupportedException("no providers installed");
10 }
11 AttachNotSupportedException lastExc = null;
12 for (AttachProvider provider: providers) {
13 try {
14 return provider.attachVirtualMachine(id);
15 } catch (AttachNotSupportedException x) {
16 lastExc = x;
17 }
18 }
19 throw lastExc;
20 }
这是attach的源码,入参为目标 JVM 的进程 ID,其实现委派给了 AttachProvider 了,通过provider.attachVirtualMachine(id);来实现真正的 attach 操作。由于 AttachProvider 是个抽象类,所以这个方法的真正实现在子类中,在 Linux 环境下,我们看 sun.tools.attach.BsdAttachProvider.java 的实现。
1 public VirtualMachine attachVirtualMachine(String vmid)
2 throws AttachNotSupportedException, IOException
3 {
4 checkAttachPermission();
5
6 // AttachNotSupportedException will be thrown if the target VM can be determined
7 // to be not attachable.
8 testAttachable(vmid);
9
10 return new BsdVirtualMachine(this, vmid);
11 }
这个方法非常简单,就是 new 了一个 BsdVirtualMachine 对象,并且把目标进程 ID 带过去了。看sun.tools.attach.BsdVirtualMachine.java 的构造函数
1 /**
2 * Attaches to the target VM
3 */
4 BsdVirtualMachine(AttachProvider provider, String vmid)
5 throws AttachNotSupportedException, IOException
6 {
7 super(provider, vmid);
8
9 // This provider only understands pids
10 int pid;
11 try {
12 pid = Integer.parseInt(vmid);
13 } catch (NumberFormatException x) {
14 throw new AttachNotSupportedException("Invalid process identifier");
15 }
16
17 // Find the socket file. If not found then we attempt to start the
18 // attach mechanism in the target VM by sending it a QUIT signal.
19 // Then we attempt to find the socket file again.
20 path = findSocketFile(pid);
21 if (path == null) {
22 File f = new File(tmpdir, ".attach_pid" + pid);
23 createAttachFile(f.getPath());
24 try {
25 sendQuitTo(pid);
26
27 // give the target VM time to start the attach mechanism
28 int i = 0;
29 long delay = 200;
30 int retries = (int)(attachTimeout() / delay);
31 do {
32 try {
33 Thread.sleep(delay);
34 } catch (InterruptedException x) { }
35 path = findSocketFile(pid);
36 i++;
37 } while (i <= retries && path == null);
38 if (path == null) {
39 throw new AttachNotSupportedException(
40 "Unable to open socket file: target process not responding " +
41 "or HotSpot VM not loaded");
42 }
43 } finally {
44 f.delete();
45 }
46 }
47
48 // Check that the file owner/permission to avoid attaching to
49 // bogus process
50 checkPermissions(path);
51
52 // Check that we can connect to the process
53 // - this ensures we throw the permission denied error now rather than
54 // later when we attempt to enqueue a command.
55 int s = socket();
56 try {
57 connect(s, path);
58 } finally {
59 close(s);
60 }
61 }
首先看20行处的findSocketFile(pid);这里是找对应的 socket (/tmp/.java_pid${pid})文件,这个文件就是我们在第二大点图中画出来的,用于进程间通信的 socket 文件,如果不存在,即第一次进入该方法的时候。这时会运行到74行的createAttachFile(f.getPath());来创建一个attach 文件,socket 文件的命名方式为:/tmp/../.attach_pid${pid},关于这两个方法(findSocketFile和createAttachFile)的具体实现,这里就不展开了,感兴趣的可以直接去查看jdk/src/solaris/native/sun/tools/attach/BsdVirtualMachine.c的相关源码。然后就会运行到一个非常关键的方法25行的sendQuitTo(pid);这个方法的实现,我们等会进入BsdVirtualMachine.c看下源码,其主要目的就是给该进程发送一个信号。之后会进入到31行处的 do...while循环,自旋反复轮询指定的次数来获取该 socket 文件的路径,直到超时或者 path(即 socket 文件路径) 不为空,最后在55行处,建立一个 socket,并且在57行处通过 path 进行 socket 的连接,从而完成了客户端(Client JVM)到目标进程(Target JVM)的 socket 通道建立。不过,请打住,这里是不是少了点什么?我相信细心的你肯定发现了,至少还存2个问题,
1. Target JVM 的 socket 服务端是何时创建的?
2. 用于通信的 socket 文件是在哪里创建的?
带着这两个问题,我们进入25行关键方法sendQuitTo(pid);的源码解读,该方法是个本地方法,位于jdk/src/solaris/native/sun/tools/attach/BsdVirtualMachine.c中
1 /*
2 * Class: sun_tools_attach_BsdVirtualMachine
3 * Method: sendQuitTo
4 * Signature: (I)V
5 */
6 JNIEXPORT void JNICALL Java_sun_tools_attach_BsdVirtualMachine_sendQuitTo
7 (JNIEnv *env, jclass cls, jint pid)
8 {
9 if (kill((pid_t)pid, SIGQUIT)) {
10 JNU_ThrowIOExceptionWithLastError(env, "kill");
11 }
12 }
看到第9行的时候,是不是觉得这里必然和前面3.1中大篇幅分析的信号处理线程“Signal Dispatcher”有种必然联系了?没错,这里就是通过 kill 这个系统调用像目标 JVM,发送了一个 SIGQUIT 的信号,该信号是个#define,即宏,表示的数字“3”,即类似在 linux 命令行执行了“kill -3 ${pid}”的操做(其实,这个命令正是获取目标 JVM 线程 dump 文件的一种方式,读者可以试试)。既然这里向目标 JVM 发送了这么个信号,那么我们现在就移步到3.1中讲到过的 signal_thread_entry 方法中去。
1 static void signal_thread_entry(JavaThread* thread, TRAPS) {
2 os::set_priority(thread, NearMaxPriority);
3 while (true) {
4 int sig;
5 {
6 // FIXME : Currently we have not decieded what should be the status
7 // for this java thread blocked here. Once we decide about
8 // that we should fix this.
9 sig = os::signal_wait();
10 }
11 if (sig == os::sigexitnum_pd()) {
12 // Terminate the signal thread
13 return;
14 }
15
16 switch (sig) {
17 case SIGBREAK: {
18 // Check if the signal is a trigger to start the Attach Listener - in that
19 // case don't print stack traces.
20 if (!DisableAttachMechanism && AttachListener::is_init_trigger()) {
21 continue;
22 }
23 // Print stack traces
24 // Any SIGBREAK operations added here should make sure to flush
25 // the output stream (e.g. tty->flush()) after output. See 4803766.
26 // Each module also prints an extra carriage return after its output.
27 VM_PrintThreads op;
28 VMThread::execute(&op);
29 VM_PrintJNI jni_op;
30 VMThread::execute(&jni_op);
31 VM_FindDeadlocks op1(tty);
32 VMThread::execute(&op1);
33 Universe::print_heap_at_SIGBREAK();
34 if (PrintClassHistogram) {
35 VM_GC_HeapInspection op1(gclog_or_tty, true /* force full GC before heap inspection */);
36 VMThread::execute(&op1);
37 }
38 if (JvmtiExport::should_post_data_dump()) {
39 JvmtiExport::post_data_dump();
40 }
41 break;
42 }
43 default: {
44 // Dispatch the signal to java
45 HandleMark hm(THREAD);
46 Klass* k = SystemDictionary::resolve_or_null(vmSymbols::sun_misc_Signal(), THREAD);
47 KlassHandle klass (THREAD, k);
48 if (klass.not_null()) {
49 JavaValue result(T_VOID);
50 JavaCallArguments args;
51 args.push_int(sig);
52 JavaCalls::call_static(
53 &result,
54 klass,
55 vmSymbols::dispatch_name(),
56 vmSymbols::int_void_signature(),
57 &args,
58 THREAD
59 );
60 }
61 if (HAS_PENDING_EXCEPTION) {
62 // tty is initialized early so we don't expect it to be null, but
63 // if it is we can't risk doing an initialization that might
64 // trigger additional out-of-memory conditions
65 if (tty != NULL) {
66 char klass_name[256];
67 char tmp_sig_name[16];
68 const char* sig_name = "UNKNOWN";
69 InstanceKlass::cast(PENDING_EXCEPTION->klass())->
70 name()->as_klass_external_name(klass_name, 256);
71 if (os::exception_name(sig, tmp_sig_name, 16) != NULL)
72 sig_name = tmp_sig_name;
73 warning("Exception %s occurred dispatching signal %s to handler"
74 "- the VM may need to be forcibly terminated",
75 klass_name, sig_name );
76 }
77 CLEAR_PENDING_EXCEPTION;
78 }
79 }
80 }
81 }
82 }
这里的17行,我们看到了有个对 SIGBREAK(宏定义) 信号处理的 case,事实上,这个SIGBREAK和前面客户端发过来的SIGQUIT 的值是一样的,都是“3”,熟悉 C语言的读者应该不难理解。所以,当客户端发送这个信号给目标 JVM 时,就理所应当的进入了这个 case 的处理逻辑。这里的27行到40行,事实上就是对“kill -3 ${pid}”执行时对应的处理逻辑“进行目标 JVM 进程的线程 dump 操作”。现在我们重点关注一下20行的 if 语句,第一个 boolean 值,某认情况下是false(可通过/hotspot/src/share/vm/runtime/globals.c)查看,表示某认情况下是不禁止attach 机制的,于是就会进入第二个条件的判断AttachListener::is_init_trigger(),这里的判断还是比较有意思的(即判断当前杀是不是需要进行 attach 的初始化操作),我们进入源码,源码的文件为:hotspot/src/os/bsd/vm/attachListener_bsd.cpp
1 // If the file .attach_pid<pid> exists in the working directory
2 // or /tmp then this is the trigger to start the attach mechanism
3 bool AttachListener::is_init_trigger() {
4 if (init_at_startup() || is_initialized()) {
5 return false; // initialized at startup or already initialized
6 }
7 char path[PATH_MAX + 1];
8 int ret;
9 struct stat st;
10
11 snprintf(path, PATH_MAX + 1, "%s/.attach_pid%d",
12 os::get_temp_directory(), os::current_process_id());
13 RESTARTABLE(::stat(path, &st), ret);
14 if (ret == 0) {
15 // simple check to avoid starting the attach mechanism when
16 // a bogus user creates the file
17 if (st.st_uid == geteuid()) {
18 init();
19 return true;
20 }
21 }
22 return false;
23 }
方法进入的第一行,即判断是不是在 JVM 启动时就初始化或者之前已经初始化过,如果是,则直接返回,否则继续当前方法。方法的第11行,是在处理/tmp/attach_pid${pid}路径(这个文件就是 Client JVM 在attach 时创建的),并把 path 传入13行定义的宏进行判断,如果这个文件存在,且刚好是当前用户的创建的 attach_pid 文件,则进入18行的 init() 方法,否则什么也不做,返回 false。接着我们进入 init 的源码(hotspot/src/share/vm/services/attachListener.cpp)
1 // Starts the Attach Listener thread
2 void AttachListener::init() {
3 EXCEPTION_MARK;
4 Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK);
5 instanceKlassHandle klass (THREAD, k);
6 instanceHandle thread_oop = klass->allocate_instance_handle(CHECK);
7
8 const char thread_name[] = "Attach Listener";
9 Handle string = java_lang_String::create_from_str(thread_name, CHECK);
10
11 // Initialize thread_oop to put it into the system threadGroup
12 Handle thread_group (THREAD, Universe::system_thread_group());
13 JavaValue result(T_VOID);
14 JavaCalls::call_special(&result, thread_oop,
15 klass,
16 vmSymbols::object_initializer_name(),
17 vmSymbols::threadgroup_string_void_signature(),
18 thread_group,
19 string,
20 THREAD);
21
22 if (HAS_PENDING_EXCEPTION) {
23 tty->print_cr("Exception in VM (AttachListener::init) : ");
24 java_lang_Throwable::print(PENDING_EXCEPTION, tty);
25 tty->cr();
26
27 CLEAR_PENDING_EXCEPTION;
28
29 return;
30 }
31
32 KlassHandle group(THREAD, SystemDictionary::ThreadGroup_klass());
33 JavaCalls::call_special(&result,
34 thread_group,
35 group,
36 vmSymbols::add_method_name(),
37 vmSymbols::thread_void_signature(),
38 thread_oop, // ARG 1
39 THREAD);
40
41 if (HAS_PENDING_EXCEPTION) {
42 tty->print_cr("Exception in VM (AttachListener::init) : ");
43 java_lang_Throwable::print(PENDING_EXCEPTION, tty);
44 tty->cr();
45
46 CLEAR_PENDING_EXCEPTION;
47
48 return;
49 }
50
51 { MutexLocker mu(Threads_lock);
52 JavaThread* listener_thread = new JavaThread(&attach_listener_thread_entry);
53
54 // Check that thread and osthread were created
55 if (listener_thread == NULL || listener_thread->osthread() == NULL) {
56 vm_exit_during_initialization("java.lang.OutOfMemoryError",
57 "unable to create new native thread");
58 }
59
60 java_lang_Thread::set_thread(thread_oop(), listener_thread);
61 java_lang_Thread::set_daemon(thread_oop());
62
63 listener_thread->set_threadObj(thread_oop());
64 Threads::add(listener_thread);
65 Thread::start(listener_thread);
66 }
67 }
从源码中,我们可以看出来,这里最主要的功能是,创建一个名为“Attach Listener”的 Java 线程,该线程启动后会调用attach_listener_thread_entry这个方法(52行),来完成有关的任务处理。进入attach_listener_thread_entry方法
1 // The Attach Listener threads services a queue. It dequeues an operation
2 // from the queue, examines the operation name (command), and dispatches
3 // to the corresponding function to perform the operation.
4
5 static void attach_listener_thread_entry(JavaThread* thread, TRAPS) {
6 os::set_priority(thread, NearMaxPriority);
7
8 thread->record_stack_base_and_size();
9
10 if (AttachListener::pd_init() != 0) {
11 return;
12 }
13 AttachListener::set_initialized();
14
15 for (;;) {
16 AttachOperation* op = AttachListener::dequeue();
17 if (op == NULL) {
18 return; // dequeue failed or shutdown
19 }
20
21 ResourceMark rm;
22 bufferedStream st;
23 jint res = JNI_OK;
24
25 // handle special detachall operation
26 if (strcmp(op->name(), AttachOperation::detachall_operation_name()) == 0) {
27 AttachListener::detachall();
28 } else {
29 // find the function to dispatch too
30 AttachOperationFunctionInfo* info = NULL;
31 for (int i=0; funcs[i].name != NULL; i++) {
32 const char* name = funcs[i].name;
33 assert(strlen(name) <= AttachOperation::name_length_max, "operation <= name_length_max");
34 if (strcmp(op->name(), name) == 0) {
35 info = &(funcs[i]);
36 break;
37 }
38 }
39
40 // check for platform dependent attach operation
41 if (info == NULL) {
42 info = AttachListener::pd_find_operation(op->name());
43 }
44
45 if (info != NULL) {
46 // dispatch to the function that implements this operation
47 res = (info->func)(op, &st);
48 } else {
49 st.print("Operation %s not recognized!", op->name());
50 res = JNI_ERR;
51 }
52 }
53
54 // operation complete - send result and output to client
55 op->complete(res, &st);
56 }
57 }
这里需要关注两个方面的内容,
第一、第10行的AttachListener::pd_init();
第二、第15行开始的 for 循环里面的内容。
首先看AttachListener::pd_init()
1 int AttachListener::pd_init() {
2 JavaThread* thread = JavaThread::current();
3 ThreadBlockInVM tbivm(thread);
4
5 thread->set_suspend_equivalent();
6 // cleared by handle_special_suspend_equivalent_condition() or
7 // java_suspend_self() via check_and_wait_while_suspended()
8
9 int ret_code = BsdAttachListener::init();
10
11 // were we externally suspended while we were waiting?
12 thread->check_and_wait_while_suspended();
13
14 return ret_code;
15 }
以上的 pd_init() 方法是在hotspot/src/os/bsd/vm/attachListener_bsd.cpp中实现的,我们看第9行的代码,调用了BsdAttachListener::init()一个这样的方法,该方法的主要作用就是生产 socket 通信文件的。源码如下
1 // Initialization - create a listener socket and bind it to a file
2
3 int BsdAttachListener::init() {
4 char path[UNIX_PATH_MAX]; // socket file
5 char initial_path[UNIX_PATH_MAX]; // socket file during setup
6 int listener; // listener socket (file descriptor)
7
8 // register function to cleanup
9 ::atexit(listener_cleanup);
10
11 int n = snprintf(path, UNIX_PATH_MAX, "%s/.java_pid%d",
12 os::get_temp_directory(), os::current_process_id());
13 if (n < (int)UNIX_PATH_MAX) {
14 n = snprintf(initial_path, UNIX_PATH_MAX, "%s.tmp", path);
15 }
16 if (n >= (int)UNIX_PATH_MAX) {
17 return -1;
18 }
19
20 // create the listener socket
21 listener = ::socket(PF_UNIX, SOCK_STREAM, 0);
22 if (listener == -1) {
23 return -1;
24 }
25
26 // bind socket
27 struct sockaddr_un addr;
28 addr.sun_family = AF_UNIX;
29 strcpy(addr.sun_path, initial_path);
30 ::unlink(initial_path);
31 int res = ::bind(listener, (struct sockaddr*)&addr, sizeof(addr));
32 if (res == -1) {
33 ::close(listener);
34 return -1;
35 }
36
37 // put in listen mode, set permissions, and rename into place
38 res = ::listen(listener, 5);
39 if (res == 0) {
40 RESTARTABLE(::chmod(initial_path, S_IREAD|S_IWRITE), res);
41 if (res == 0) {
42 // make sure the file is owned by the effective user and effective group
43 // (this is the default on linux, but not on mac os)
44 RESTARTABLE(::chown(initial_path, geteuid(), getegid()), res);
45 if (res == 0) {
46 res = ::rename(initial_path, path);
47 }
48 }
49 }
50 if (res == -1) {
51 ::close(listener);
52 ::unlink(initial_path);
53 return -1;
54 }
55 set_path(path);
56 set_listener(listener);
57
58 return 0;
59 }
从方法的注释,就能看出这个方法就是用来创建一个基于文件的 socket 的 listener 端,即服务端的。具体的创建过程,代码写的已经很清楚了,我做下简单描述,11行处,构建 socket 通信文件的路径(/tmp/.java_pid${pid}),21 行,创建一个 socket,其中关注 socket 函数的第一个参数,当为 PF_UNIX 时,表示创建文件 socket,详情可以参考 linux 的 socket 函数说明,然后到29行,将 socket 文件 path 拷贝到 socket 通信地址中,即以此文件作为通信地址,然后在31行时,将 socket 和该 socket 文件地址做一个绑定,38行,表示对当前 socket 进行监听(数字5表示监听时可容纳客户端连接的队列的大小),如果有Client JVM 的客户端连接上来,并且发送了相关消息,该服务端就可以对其进行相应处理了。至此,进程间 socket 的通信的通道就建立了。
其次看下 for 循环做了什么?其实很简单,16行,BsdAttachListener::dequeue() 从监听器的队列中拿到一个 Client JVM 的AttachOperation(当客户端 attach 上 target JVM 之后,往目标 JVM 发送任意 socket 信息,都会被放置到这个队列中,等待被处理),此处会被阻塞,直到收到请求,如下源码的13行, socket 的 accept 函数处于等待状态,等待来之客户端 JVM 的相关请求,一旦获取到请求,则将请求组装好返回给调用者一个BadAttachOperation 对象。
1 // Dequeue an operation
2 //
3 // In the Bsd implementation there is only a single operation and clients
4 // cannot queue commands (except at the socket level).
5 //
6 BsdAttachOperation* BsdAttachListener::dequeue() {
7 for (;;) {
8 int s;
9
10 // wait for client to connect
11 struct sockaddr addr;
12 socklen_t len = sizeof(addr);
13 RESTARTABLE(::accept(listener(), &addr, &len), s);
14 if (s == -1) {
15 return NULL; // log a warning?
16 }
17
18 // get the credentials of the peer and check the effective uid/guid
19 // - check with jeff on this.
20 uid_t puid;
21 gid_t pgid;
22 if (::getpeereid(s, &puid, &pgid) != 0) {
23 ::close(s);
24 continue;
25 }
26 uid_t euid = geteuid();
27 gid_t egid = getegid();
28
29 if (puid != euid || pgid != egid) {
30 ::close(s);
31 continue;
32 }
33
34 // peer credential look okay so we read the request
35 BsdAttachOperation* op = read_request(s);
36 if (op == NULL) {
37 ::close(s);
38 continue;
39 } else {
40 return op;
41 }
42 }
43 }
所以,只要收到一个AttachOperation不为“detachall”的操纵请求就会进入到45行处进行处理,这里的目的就是为了拿到对应的操作AttachOperationFunctionInfo对象,如果不为空,则调用其func,来完成对客户端的响应,如47行所示。AttachOperationFunctionInfo(/hotspot/src/share/vm/services/attachListener.cpp)的定义如下
1 // names must be of length <= AttachOperation::name_length_max
2 static AttachOperationFunctionInfo funcs[] = {
3 { "agentProperties", get_agent_properties },
4 { "datadump", data_dump },
5 { "dumpheap", dump_heap },
6 { "load", JvmtiExport::load_agent_library },
7 { "properties", get_system_properties },
8 { "threaddump", thread_dump },
9 { "inspectheap", heap_inspection },
10 { "setflag", set_flag },
11 { "printflag", print_flag },
12 { "jcmd", jcmd },
13 { NULL, NULL }
14 };
从这里,我们可以看到,threaddump、dumpheap 等我们常用的操纵。到此为止,水落石出,涉及到 attach 操纵的服务端的原理基本已经理清楚了。接下来我们以 jstack 为例,来看下客户端 JVM 是不是确实是以我们上面分析出来的方式与服务端 JVM 进行通信,并获取到它想要的内容的。
JVM 对 Attach 上来的进程的命令的响应,以 jstack -l 为例
我们首先进入 jstack 的源码,源码目录为jdk/src/share/classes/sun/tools/jstack/JStack.java。进入 main 函数
1 public static void main(String[] args) throws Exception {
2 if (args.length == 0) {
3 usage(1); // no arguments
4 }
5
6 boolean useSA = false;
7 boolean mixed = false;
8 boolean locks = false;
9
10 // Parse the options (arguments starting with "-" )
11 int optionCount = 0;
12 while (optionCount < args.length) {
13 String arg = args[optionCount];
14 if (!arg.startsWith("-")) {
15 break;
16 }
17 if (arg.equals("-help") || arg.equals("-h")) {
18 usage(0);
19 }
20 else if (arg.equals("-F")) {
21 useSA = true;
22 }
23 else {
24 if (arg.equals("-m")) {
25 mixed = true;
26 } else {
27 if (arg.equals("-l")) {
28 locks = true;
29 } else {
30 usage(1);
31 }
32 }
33 }
34 optionCount++;
35 }
36
37 // mixed stack implies SA tool
38 if (mixed) {
39 useSA = true;
40 }
41
42 // Next we check the parameter count. If there are two parameters
43 // we assume core file and executable so we use SA.
44 int paramCount = args.length - optionCount;
45 if (paramCount == 0 || paramCount > 2) {
46 usage(1);
47 }
48 if (paramCount == 2) {
49 useSA = true;
50 } else {
51 // If we can't parse it as a pid then it must be debug server
52 if (!args[optionCount].matches("[0-9]+")) {
53 useSA = true;
54 }
55 }
56
57 // now execute using the SA JStack tool or the built-in thread dumper
58 if (useSA) {
59 // parameters (<pid> or <exe> <core>
60 String params[] = new String[paramCount];
61 for (int i=optionCount; i<args.length; i++ ){
62 params[i-optionCount] = args[i];
63 }
64 runJStackTool(mixed, locks, params);
65 } else {
66 // pass -l to thread dump operation to get extra lock info
67 String pid = args[optionCount];
68 String params[];
69 if (locks) {
70 params = new String[] { "-l" };
71 } else {
72 params = new String[0];
73 }
74 runThreadDump(pid, params);
75 }
76 }
当采用 jstack -l 时,会走65行的 else 分支,最终执行77行的runThreadDump方法
1 // Attach to pid and perform a thread dump
2 private static void runThreadDump(String pid, String args[]) throws Exception {
3 VirtualMachine vm = null;
4 try {
5 vm = VirtualMachine.attach(pid);
6 } catch (Exception x) {
7 String msg = x.getMessage();
8 if (msg != null) {
9 System.err.println(pid + ": " + msg);
10 } else {
11 x.printStackTrace();
12 }
13 if ((x instanceof AttachNotSupportedException) &&
14 (loadSAClass() != null)) {
15 System.err.println("The -F option can be used when the target " +
16 "process is not responding");
17 }
18 System.exit(1);
19 }
20
21 // Cast to HotSpotVirtualMachine as this is implementation specific
22 // method.
23 InputStream in = ((HotSpotVirtualMachine)vm).remoteDataDump((Object[])args);
24
25 // read to EOF and just print output
26 byte b[] = new byte[256];
27 int n;
28 do {
29 n = in.read(b);
30 if (n > 0) {
31 String s = new String(b, 0, n, "UTF-8");
32 System.out.print(s);
33 }
34 } while (n > 0);
35 in.close();
36 vm.detach();
37 }
5行:执行VirtualMachine.attach(pid);则会达到3.1、3.2的效果,即服务端已经做好了所有 attach 所需的准备,如 socket 服务端、socket 通信文件、socket 请求处理线程“Attach Listener”。
23行:通过调用 HotSpotVirtualMachine 对象的 remoteDataDump 函数进行远程 dump,获得输入流 InputStream in,最后通过读取输入流的内容,来通过标准输出流输出从服务端获取的数据。至此,jstack -l 命令完成所有操作。
接下来,我们重点分析HotSpotVirtualMachine 对象的 remoteDataDump 函数。首先上HotSpotVirtualMachine(/jdk/src/share/classes/sun/tools/attach/HotSpotVirtualMachine.java) 对象的 remoteDataDump的源码
1 // Remote ctrl-break. The output of the ctrl-break actions can
2 // be read from the input stream.
3 public InputStream remoteDataDump(Object ... args) throws IOException {
4 return executeCommand("threaddump", args);
5 }
请注意4行的 cmd 字符串为“threaddump”,这个和3.2中 AttachOperationFunctionInfo 的定义是吻合的,也就是说最终在服务端会调用 thread_dump 方法,来执行线程 dump,并将结果返回给客户端。接着我们看下下 executeCommand方法,该方法只是简单的调用 execute 方法,如下
1 /*
2 * Convenience method for simple commands
3 */
4 private InputStream executeCommand(String cmd, Object ... args) throws IOException {
5 try {
6 return execute(cmd, args);
7 } catch (AgentLoadException x) {
8 throw new InternalError("Should not get here", x);
9 }
10 }
exectue 方法在该类中为抽象方法,其具体实现放在了sun.tools.attach.BsdVirtualMachine.java中,我们看下在其具体实现这里可是最最关键的地方了
1 /**
2 * Execute the given command in the target VM.
3 */
4 InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {
5 assert args.length <= 3; // includes null
6
7 // did we detach?
8 String p;
9 synchronized (this) {
10 if (this.path == null) {
11 throw new IOException("Detached from target VM");
12 }
13 p = this.path;
14 }
15
16 // create UNIX socket
17 int s = socket();
18
19 // connect to target VM
20 try {
21 connect(s, p);
22 } catch (IOException x) {
23 close(s);
24 throw x;
25 }
26
27 IOException ioe = null;
28
29 // connected - write request
30 // <ver> <cmd> <args...>
31 try {
32 writeString(s, PROTOCOL_VERSION);
33 writeString(s, cmd);
34
35 for (int i=0; i<3; i++) {
36 if (i < args.length && args[i] != null) {
37 writeString(s, (String)args[i]);
38 } else {
39 writeString(s, "");
40 }
41 }
42 } catch (IOException x) {
43 ioe = x;
44 }
45
46
47 // Create an input stream to read reply
48 SocketInputStream sis = new SocketInputStream(s);
49
50 // Read the command completion status
51 int completionStatus;
52 try {
53 completionStatus = readInt(sis);
54 } catch (IOException x) {
55 sis.close();
56 if (ioe != null) {
57 throw ioe;
58 } else {
59 throw x;
60 }
61 }
62
63 if (completionStatus != 0) {
64 sis.close();
65
66 // In the event of a protocol mismatch then the target VM
67 // returns a known error so that we can throw a reasonable
68 // error.
69 if (completionStatus == ATTACH_ERROR_BADVERSION) {
70 throw new IOException("Protocol mismatch with target VM");
71 }
72
73 // Special-case the "load" command so that the right exception is
74 // thrown.
75 if (cmd.equals("load")) {
76 throw new AgentLoadException("Failed to load agent library");
77 } else {
78 throw new IOException("Command failed in target VM");
79 }
80 }
81
82 // Return the input stream so that the command output can be read
83 return sis;
84 }
17行:创建一个 socket,这个是 socket 是一个 jni 本地方法,有兴趣的可以去看对应的实现,源码在jdk/src/solaris/native/sun/tools/attach/BsdVirtualMachine.c中,其关键操作就一个return socket(PF_UNIX, SOCK_STREAM, 0) 客户端 socket 连接。
21行:这里也是一个本地方法,调用了 connect(s,p),这里的 p 就是 attach 时产生的/tmp/.java_pid${pid}的 socket 文件路径,这样,客户端就和目标 JVM 连接上了,该方法同样是一个 native 方法,可以通过查看BsdVirtualMachine.c的源码来进行查看,如下,重点在16行使用 socket 文件路径作为连接地址 和 18 行与目标 JVM 端启动的 socket server 建立连接;
1 /*
2 * Class: sun_tools_attach_BsdVirtualMachine
3 * Method: connect
4 * Signature: (ILjava/lang/String;)I
5 */
6 JNIEXPORT void JNICALL Java_sun_tools_attach_BsdVirtualMachine_connect
7 (JNIEnv *env, jclass cls, jint fd, jstring path)
8 {
9 jboolean isCopy;
10 const char* p = GetStringPlatformChars(env, path, &isCopy);
11 if (p != NULL) {
12 struct sockaddr_un addr;
13 int err = 0;
14
15 addr.sun_family = AF_UNIX;
16 strcpy(addr.sun_path, p);
17
18 if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
19 err = errno;
20 }
21
22 if (isCopy) {
23 JNU_ReleaseStringPlatformChars(env, path, p);
24 }
25
26 /*
27 * If the connect failed then we throw the appropriate exception
28 * here (can't throw it before releasing the string as can't call
29 * JNI with pending exception)
30 */
31 if (err != 0) {
32 if (err == ENOENT) {
33 JNU_ThrowByName(env, "java/io/FileNotFoundException", NULL);
34 } else {
35 char* msg = strdup(strerror(err));
36 JNU_ThrowIOException(env, msg);
37 if (msg != NULL) {
38 free(msg);
39 }
40 }
41 }
42 }
31~44行:想 Target JVM 端发送命令 threaddump、以及可能存在的相关参数,如-l;这里的 writeString 同样是一个本地方法,涉及到的底层操作就是一个 C 语言库的 write 操作,感兴趣的可以自己看源码,不再赘述;
48~83行:这里就是对当前 socket 连接,构建一个 SocketInputStream 对象,并等待Target JVM 端数据完全返回,最后将这个 InputStream 对象作为方法返回参数返回。
总结
本文结合 Attach 的原理和使用案例(jstack -l),对 Attach 的各个方面都进行了深入的分析和总结,希望能对有需要的同学有所帮助。当然,以上均为本人个人所学,所以难免会有错误和疏忽的地方,如果您发现了,还麻烦指出。
JVM Attach实现原理剖析的更多相关文章
- NameNode和SecondaryNameNode工作原理剖析
NameNode和SecondaryNameNode工作原理剖析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.NameNode中的元数据是存储在那里的? 1>.首先,我 ...
- 《java学习三》并发编程 -------线程池原理剖析
阻塞队列与非阻塞队 阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞.试图从空的阻塞队列中获取元素的线程将会被阻塞,直到 ...
- 46、Spark SQL工作原理剖析以及性能优化
一.工作原理剖析 1.图解 二.性能优化 1.设置Shuffle过程中的并行度:spark.sql.shuffle.partitions(SQLContext.setConf()) 2.在Hive数据 ...
- java的加载与执行原理剖析
到目前为止,我们接触过的重点术语,总结一下: Java体系的技术被划分为三大块: JavaSE:标准版 JavaEE:企业版 JavaME:微型版 安装JDK之后: JDK:java开发工具箱 JRE ...
- synchronized原理剖析
synchronized原理剖析 并发编程存在什么问题? 1️⃣ 可见性 可见性:是指当一个线程对共享变量进行了修改,那么另外的线程可以立即看到修改后的最新值. 案例演示:一个线程A根据 boolea ...
- ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)
ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...
- ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行
ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...
- 【Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析】
原文:[Xamarin挖墙脚系列:Xamarin.IOS机制原理剖析] [注意:]团队里总是有人反映卸载Xamarin,清理不完全.之前写过如何完全卸载清理剩余的文件.今天写了Windows下的批命令 ...
- 【Xamarin 跨平台机制原理剖析】
原文:[Xamarin 跨平台机制原理剖析] [看了请推荐,推荐满100后,将发补丁地址] Xamarin项目从喊口号到现在,好几个年头了,在内地没有火起来,原因无非有三,1.授权费贵 2.贵 3.原 ...
随机推荐
- SonarQube+jenkins-自动化持续代码扫描
SonarQube+jenkins-自动化持续代码扫描 1.SonarQube 1.1 SonarQube介绍 1.1.1 SonarQube 工作流程 1. 2 Docker方式安装SonarQub ...
- Spark动态加载外部资源文件
Spark动态加载外部资源文件 1.spark-submit --files 动态加载外部资源文件 之前做一个关于Spark的项目时,因项目中需要读取某个静态资源文件,然后在本地IDEA测试一切皆正常 ...
- c++nullptr(空指针常量)、constexpr(常量表达式)
总述 又来更新了,今天带来的是nullptr空指针常量.constexpr(常量表达式)C++的两个用法.Result result_fun = nullptr;constexpr stati ...
- 各个复位标志解析,让我们对MCU的程序的健康更有把控
作者:良知犹存 转载授权以及围观:欢迎添加微信公众号:Conscience_Remains 总述 曾经开发的时候遇到这样情况,我们开发的设备需要长时间工作上报信息,但是我们在后台查看上报数据,发现设备 ...
- ProBuilder快速原型开发技术 ---操作基础
本篇文章笔者对ProBuilder(以下简称:PB),进行操作基础的介绍. 一:PB大小图标显示方式 PB操作面板有两种显示方式:小图标与大图标.大图标优点是显示清晰操作方便,缺点是没有更加精确的参数 ...
- D - Seek the Name, Seek the Fame
The little cat is so famous, that many couples tramp over hill and dale to Byteland, and asked the l ...
- Codeforces Round #638 (Div. 2)
Contest Info Practice Link Solved A B C D E F 4/6 O O Ø Ø O 在比赛中通过 Ø 赛后通过 ! 尝试了但是失败了 - 没有尝试 Sol ...
- P2805 [NOI2009]植物大战僵尸 (拓扑排序 + 最小割)
题意:N*M的矩阵 每个点上都有一颗植物 僵尸只能从每一行的最右边向左进攻 每个植物有攻击范围 可以保护在攻击范围内的植物 同时每一颗植物也保护他左边的植物 摧毁每个植物能获得价值 如果这个植物被保护 ...
- 2019牛客多校 Round8
Solved:3 Rank:261 E Explorer (线段树) 题意:n个点 m条边 每条边只有身高l,r内的人可以穿过 问有几种身高可以从1走到n 题解:把l,r离散化后(左闭右开) 线段树叶 ...
- Codeforces #640 div4 F~G (构造二连弹)
题意:求一个只由\(01\)组成的字符串,使得它所有长度为\(2\)的子串满足:每对子串的数字和为\(0,1,2\)的个数为\(a,b,c\). 题解:我们先考虑子串数字和为\(1\)的情况,构造出一 ...