HotSpot端源码
我们的目的是实现外部进程发送一个attach的信号,JVM收到后加载指定的agent,本质就在于外部进程与JVM的通信。
所以首先来分析JVM端的源码,看看它给我们提供了一些什么样的接口。
源码在OpenJdk下的HotSpot包,有关源码目录介绍可参考:OpenJDK 源码的目录结构
- Signal Dispather的创建
要实现进程到JVM的通信,目标JVM会启动一个监听线程Signal Dispatcher,监听外部进程给JVM发送的信号。
首先以Thread.cpp中的create_vm开始看起,可在方法中看到:
(hotspot/src/share/vm/runtime/Thread.cpp)
// Signal Dispatcher needs to be started before VMInit event is posted
os::signal_init(CHECK_JNI_ERR);
// Start Attach Listener if +StartAttachListener or it can't be started lazily
if (!DisableAttachMechanism) {
AttachListener::vm_start();
//删除所有的.java_pid形式的文件
if (StartAttachListener || AttachListener::init_at_startup()) {
AttachListener::init();
}
}
其中的signal_init():
(hotspot/src/share/vm/runtime/os.cpp)
void os::signal_init(TRAPS) {
if (!ReduceSignalUsage) {
// Setup JavaThread for processing signals
Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK);
InstanceKlass* ik = InstanceKlass::cast(k);
instanceHandle thread_oop = ik->allocate_instance_handle(CHECK);
const char thread_name[] = "Signal Dispatcher";
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,
ik,
vmSymbols::object_initializer_name(),
vmSymbols::threadgroup_string_void_signature(),
thread_group,
string,
CHECK);
Klass* group = SystemDictionary::ThreadGroup_klass();
JavaCalls::call_special(&result,
thread_group,
group,
vmSymbols::add_method_name(),
vmSymbols::thread_void_signature(),
thread_oop, // ARG 1
CHECK);
os::signal_init_pd();
{ MutexLocker mu(Threads_lock);
JavaThread* signal_thread = new JavaThread(&signal_thread_entry);
// At this point it may be possible that no osthread was created for the
// JavaThread due to lack of memory. We would have to throw an exception
// in that case. However, since this must work and we do not allow
// exceptions anyway, check and abort if this fails.
if (signal_thread == NULL || signal_thread->osthread() == NULL) {
vm_exit_during_initialization("java.lang.OutOfMemoryError",
os::native_thread_creation_failed_msg());
}
java_lang_Thread::set_thread(thread_oop(), signal_thread);
java_lang_Thread::set_priority(thread_oop(), NearMaxPriority);
java_lang_Thread::set_daemon(thread_oop());
signal_thread->set_threadObj(thread_oop());
Threads::add(signal_thread);
Thread::start(signal_thread);
}
// Handle ^BREAK
os::signal(SIGBREAK, os::user_handler());
}
}
重点在于这行代码:JavaThread* signal_thread = new JavaThread(&signal_thread_entry);创建了一个名为Signal Dispather的线程,执行signal_thread_entry:
- Signal Dispather的执行
(hotspot/src/share/vm/runtime/os.cpp)
static void signal_thread_entry(JavaThread* thread, TRAPS) {
os::set_priority(thread, NearMaxPriority);
while (true) {
int sig;
{
// FIXME : Currently we have not decided 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(tty, true /* force full GC before heap inspection */);
VMThread::execute(&op1);
}
if (JvmtiExport::should_post_data_dump()) {
JvmtiExport::post_data_dump();
}
break;
}
default: {
// Dispatch the signal to java
HandleMark hm(THREAD);
Klass* klass = SystemDictionary::resolve_or_null(vmSymbols::jdk_internal_misc_Signal(), THREAD);
if (klass != NULL) {
JavaValue result(T_VOID);
JavaCallArguments args;
args.push_int(sig);
JavaCalls::call_static(
&result,
klass,
vmSymbols::dispatch_name(),
vmSymbols::int_void_signature(),
&args,
THREAD
);
}
if (HAS_PENDING_EXCEPTION) {
// tty is initialized early so we don't expect it to be null, but
// if it is we can't risk doing an initialization that might
// trigger additional out-of-memory conditions
if (tty != NULL) {
char klass_name[256];
char tmp_sig_name[16];
const char* sig_name = "UNKNOWN";
InstanceKlass::cast(PENDING_EXCEPTION->klass())->
name()->as_klass_external_name(klass_name, 256);
if (os::exception_name(sig, tmp_sig_name, 16) != NULL)
sig_name = tmp_sig_name;
warning("Exception %s occurred dispatching signal %s to handler"
"- the VM may need to be forcibly terminated",
klass_name, sig_name );
}
CLEAR_PENDING_EXCEPTION;
}
}
}
}
}
代码中的signal_wait()阻塞了当前线程,并等待一个信号。
当收到的信号为"SIGBREAK"时,执行is_init_trigger:
(hotspot/src/os/bsd/vm/attachListener_linux.cpp)
// If the file .attach_pid<pid> exists in the working directory
// or /tmp then this is the trigger to start the attach mechanism
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) {
log_trace(attach)("Failed to find attach file: %s, trying alternate", fn);
snprintf(fn, sizeof(fn), "%s/.attach_pid%d",
os::get_temp_directory(), os::current_process_id());
RESTARTABLE(::stat64(fn, &st), ret);
if (ret == -1) {
log_debug(attach)("Failed to find attach file: %s", fn);
}
}
if (ret == 0) {
// simple check to avoid starting the attach mechanism when
// a bogus user creates the file
if (st.st_uid == geteuid()) {
init();
log_trace(attach)("Attach trigerred by %s", fn);
return true;
} else {
log_debug(attach)("File %s has wrong user id %d (vs %d). Attach is not trigerred", fn, st.st_uid, geteuid());
}
}
return false;
}
如果找到了/tmp/.attach_pid文件,并且就是由外部进程的用户创建的,执行init()方法,方法中创建了Attach Listener线程:
- Attach Listener的创建
(hotspot/src/share/vm/services/attachListener.cpp)
// Starts the Attach Listener thread
void AttachListener::init() {
EXCEPTION_MARK;
Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, THREAD);
if (has_init_error(THREAD)) {
return;
}
InstanceKlass* klass = InstanceKlass::cast(k);
instanceHandle thread_oop = klass->allocate_instance_handle(THREAD);
if (has_init_error(THREAD)) {
return;
}
const char thread_name[] = "Attach Listener";
Handle string = java_lang_String::create_from_str(thread_name, THREAD);
if (has_init_error(THREAD)) {
return;
}
// 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,
THREAD);
if (has_init_error(THREAD)) {
return;
}
Klass* group = SystemDictionary::ThreadGroup_klass();
JavaCalls::call_special(&result,
thread_group,
group,
vmSymbols::add_method_name(),
vmSymbols::thread_void_signature(),
thread_oop, // ARG 1
THREAD);
if (has_init_error(THREAD)) {
return;
}
{ 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",
os::native_thread_creation_failed_msg());
}
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);
}
}
重点在于这行代码:JavaThread* listener_thread = new JavaThread(&attach_listener_thread_entry);创建了一个线程,执行attach_listener_thread_entry:
- Attach Listener的执行
(hotspot/src/share/vm/services/attachListener.cpp)
// The Attach Listener threads services a queue. It dequeues an operation
// from the queue, examines the operation name (command), and dispatches
// to the corresponding function to perform the operation.
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 if (!EnableDynamicAgentLoading && strcmp(op->name(), "load") == 0) {
st.print("Dynamic agent loading is not enabled. "
"Use -XX:+EnableDynamicAgentLoading to launch target VM.");
res = JNI_ERR;
} 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);
}
}
- 首先执行pd_init,pd即platform dependence,意义为不同平台中的init方法,以Linux为例:
(hotspot/src/os/bsd/vm/attachListener_linux.cpp)
int AttachListener::pd_init() {
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()
int ret_code = LinuxAttachListener::init();
// were we externally suspended while we were waiting?
thread->check_and_wait_while_suspended();
return ret_code;
}
// Initialization - create a listener socket and bind it to a file
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) {
::close(listener);
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) {
::close(listener);
::unlink(initial_path);
return -1;
}
set_path(path);
set_listener(listener);
return 0;
}
该方法的目的主要是建立一个文件/tmp/.java_pid,并创建以此为通信文件的socket,即listener。
- 在pd_init工作准备好之后,attach_listener_thread_entry进入到一个for循环,通过dequeue()取出操作
(hotspot/src/os/bsd/vm/attachListener_linux.cpp)
// Dequeue an operation
//
// In the Linux implementation there is only a single operation and clients
// cannot queue commands (except at the socket level).
//
LinuxAttachOperation* LinuxAttachListener::dequeue() {
for (;;) {
int s;
// wait for client to connect
struct sockaddr addr;
socklen_t len = sizeof(addr);
RESTARTABLE(::accept(listener(), &addr, &len), s);
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) {
::close(s);
continue;
}
uid_t euid = geteuid();
gid_t egid = getegid();
if (cred_info.uid != euid || cred_info.gid != egid) {
::close(s);
continue;
}
// peer credential look okay so we read the request
LinuxAttachOperation* op = read_request(s);
if (op == NULL) {
::close(s);
continue;
} else {
return op;
}
}
}
其中的RESTARTABLE(::accept(listener(), &addr, &len), s);为一个while循环,把等待到的客户端连接赋值给s;
通过LinuxAttachOperation* op = read_request(s);得到一个operation并返回。
- 取出operation之后,根据一个名字-方法映射表找到需要执行的方法:
(hotspot/src/share/vm/services/attachListener.cpp)
static AttachOperationFunctionInfo funcs[] = {
{ "agentProperties", get_agent_properties },
{ "datadump", data_dump },
{ "dumpheap", dump_heap },
{ "load", load_agent },
{ "properties", get_system_properties },
{ "threaddump", thread_dump },
{ "inspectheap", heap_inspection },
{ "setflag", set_flag },
{ "printflag", print_flag },
{ "jcmd", jcmd },
{ NULL, NULL }
};
比如发来的命令为"load",则执行load_agent方法:
(hotspot/src/share/vm/services/attachListener.cpp)
// Implementation of "load" command.
static jint load_agent(AttachOperation* op, outputStream* out) {
// get agent name and options
const char* agent = op->arg(0);
const char* absParam = op->arg(1);
const char* options = op->arg(2);
// If loading a java agent then need to ensure that the java.instrument module is loaded
if (strcmp(agent, "instrument") == 0) {
Thread* THREAD = Thread::current();
ResourceMark rm(THREAD);
HandleMark hm(THREAD);
JavaValue result(T_OBJECT);
Handle h_module_name = java_lang_String::create_from_str("java.instrument", THREAD);
JavaCalls::call_static(&result,
SystemDictionary::module_Modules_klass(),
vmSymbols::loadModule_name(),
vmSymbols::loadModule_signature(),
h_module_name,
THREAD);
if (HAS_PENDING_EXCEPTION) {
java_lang_Throwable::print(PENDING_EXCEPTION, out);
CLEAR_PENDING_EXCEPTION;
return JNI_ERR;
}
}
return JvmtiExport::load_agent_library(agent, absParam, options, out);
}
接收到的三个参数对应我们发送命令时的三个:“instrument”、是否绝对路径、agentpath=options;
先加载java.instrument模块,再加载Agent,调用JvmtiExport::load_agent_library(agent, absParam, options, out),当成功时返回码为0.
流程图
总结来说,JVM的代码中,核心线程有两个
-
线程1:Signal Dispatcher
创建:JVM启动时
运行:阻塞,直到收到os信号 -
线程2:Attach Listener
创建:当Signal Dispatcher收到Sigbreak信号,且找到外部进程创建的.attach_pid文件
运行:创建socket(以.java_pid通信),通过accept等待客户端的连接,读取命令 -
文件1:.attach_pid
创建:外部进程
作用:JVM收到Sigbreak信号后,确认文件是否就是由此外部进程创建的,以防止错认其他的进程发来的Sigbreak信号 -
文件2:.java_pid
创建:JVM自身
作用:建立与客户端的通信,接收命令,返回结果