Java Agent(三)OpenJdk/HotSpot Attach部分源码分析

2024-02-23 11:18

本文主要是介绍Java Agent(三)OpenJdk/HotSpot Attach部分源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

HotSpot端源码

我们的目的是实现外部进程发送一个attach的信号,JVM收到后加载指定的agent,本质就在于外部进程与JVM的通信。
所以首先来分析JVM端的源码,看看它给我们提供了一些什么样的接口。
源码在OpenJdk下的HotSpot包,有关源码目录介绍可参考:OpenJDK 源码的目录结构

  1. 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 signalsKlass* 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 threadGroupHandle 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 1CHECK);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 ^BREAKos::signal(SIGBREAK, os::user_handler());}
}

重点在于这行代码:JavaThread* signal_thread = new JavaThread(&signal_thread_entry);创建了一个名为Signal Dispather的线程,执行signal_thread_entry:

  1. 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 threadreturn;}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 javaHandleMark 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 conditionsif (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 fileif (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线程:

  1. 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 threadGroupHandle 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 1THREAD);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 createdif (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:

  1. 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 operationif (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 tooAttachOperationFunctionInfo* 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 operationif (info == NULL) {info = AttachListener::pd_find_operation(op->name());}if (info != NULL) {// dispatch to the function that implements this operationres = (info->func)(op, &st);} else {st.print("Operation %s not recognized!", op->name());res = JNI_ERR;}}// operation complete - send result and output to clientop->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 fileint LinuxAttachListener::init() {char path[UNIX_PATH_MAX];          // socket filechar initial_path[UNIX_PATH_MAX];  // socket file during setupint 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 socketlistener = ::socket(PF_UNIX, SOCK_STREAM, 0);if (listener == -1) {return -1;}// bind socketstruct 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 placeres = ::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 connectstruct 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 requestLinuxAttachOperation* 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 optionsconst 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 loadedif (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自身
    作用:建立与客户端的通信,接收命令,返回结果

这篇关于Java Agent(三)OpenJdk/HotSpot Attach部分源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/738446

相关文章

Java如何从Redis中批量读取数据

《Java如何从Redis中批量读取数据》:本文主要介绍Java如何从Redis中批量读取数据的情况,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一.背景概述二.分析与实现三.发现问题与屡次改进3.1.QPS过高而且波动很大3.2.程序中断,抛异常3.3.内存消

SpringBoot使用ffmpeg实现视频压缩

《SpringBoot使用ffmpeg实现视频压缩》FFmpeg是一个开源的跨平台多媒体处理工具集,用于录制,转换,编辑和流式传输音频和视频,本文将使用ffmpeg实现视频压缩功能,有需要的可以参考... 目录核心功能1.格式转换2.编解码3.音视频处理4.流媒体支持5.滤镜(Filter)安装配置linu

Apache 高级配置实战之从连接保持到日志分析的完整指南

《Apache高级配置实战之从连接保持到日志分析的完整指南》本文带你从连接保持优化开始,一路走到访问控制和日志管理,最后用AWStats来分析网站数据,对Apache配置日志分析相关知识感兴趣的朋友... 目录Apache 高级配置实战:从连接保持到日志分析的完整指南前言 一、Apache 连接保持 - 性

在Spring Boot中实现HTTPS加密通信及常见问题排查

《在SpringBoot中实现HTTPS加密通信及常见问题排查》HTTPS是HTTP的安全版本,通过SSL/TLS协议为通讯提供加密、身份验证和数据完整性保护,下面通过本文给大家介绍在SpringB... 目录一、HTTPS核心原理1.加密流程概述2.加密技术组合二、证书体系详解1、证书类型对比2. 证书获

Java使用MethodHandle来替代反射,提高性能问题

《Java使用MethodHandle来替代反射,提高性能问题》:本文主要介绍Java使用MethodHandle来替代反射,提高性能问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录一、认识MethodHandle1、简介2、使用方式3、与反射的区别二、示例1、基本使用2、(重要)

Java实现本地缓存的常用方案介绍

《Java实现本地缓存的常用方案介绍》本地缓存的代表技术主要有HashMap,GuavaCache,Caffeine和Encahche,这篇文章主要来和大家聊聊java利用这些技术分别实现本地缓存的方... 目录本地缓存实现方式HashMapConcurrentHashMapGuava CacheCaffe

SpringBoot整合Sa-Token实现RBAC权限模型的过程解析

《SpringBoot整合Sa-Token实现RBAC权限模型的过程解析》:本文主要介绍SpringBoot整合Sa-Token实现RBAC权限模型的过程解析,本文给大家介绍的非常详细,对大家的学... 目录前言一、基础概念1.1 RBAC模型核心概念1.2 Sa-Token核心功能1.3 环境准备二、表结

eclipse如何运行springboot项目

《eclipse如何运行springboot项目》:本文主要介绍eclipse如何运行springboot项目问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目js录当在eclipse启动spring boot项目时出现问题解决办法1.通过cmd命令行2.在ecl

Java中的Closeable接口及常见问题

《Java中的Closeable接口及常见问题》Closeable是Java中的一个标记接口,用于表示可以被关闭的对象,它定义了一个标准的方法来释放对象占用的系统资源,下面给大家介绍Java中的Clo... 目录1. Closeable接口概述2. 主要用途3. 实现类4. 使用方法5. 实现自定义Clos

Linux中的more 和 less区别对比分析

《Linux中的more和less区别对比分析》在Linux/Unix系统中,more和less都是用于分页查看文本文件的命令,但less是more的增强版,功能更强大,:本文主要介绍Linu... 目录1. 基础功能对比2. 常用操作对比less 的操作3. 实际使用示例4. 为什么推荐 less?5.