Java Attach Mechanism
Contents
We often use tools like JStack to dump threads of our java programs, or use profiling tools like Async-Profiler to profile java processes. You may wonder how these tools work, or specifically how these tools communicate with target Java processes.
Today, we’ll uncover the mystery and talk about an important mechanism provided by JVM: Attach Mechanism, which is used to enable communication between processes in and out of JVM.
What is Java attach
When we use JStack to dump out threads, we may find Attach Listener
and Signal Dispatcher
, these 2 threads are the key threads we’ll talk about today.
|
|
Through Java Attach Mechanism, we could launch a process to communicate with target Java process, to let it dump threads, dump heaps, print flags or many other options. Registered functions that JVM supported after attaching are as following.
|
|
How does attach work
Basically, there’re only a few fixed method to enable IPC(inter-process communication) in linux, such as Pipe, Shared Memory, Unix Domain Socket, etc, here JVM implementation uses UDS(Unix Domain Socket).
So there have to be a UDS server launched by JVM, which listens a socket. In fact, Attach Listener thread is responsible for this job, as showed in the thread-dump output before.
|
|
However, Attach Listener thread may not exist when JVM starts, which means
JVM won’t start an UDS server as soon as it starts. It won’t launch the server
until a SIGQUIT
signal is recieved, which indicates an UDS server is needed.
And that’s why a Signal Dispatcher thread is created as soon as JVM starts, which can also be found in the thread-dump output before.
|
|
After UDS server being ready, JVM could communicate with other process in some simple protocol. Let’s see some details in the source code.
Source code walk through
How does “Attach Listener” thread start
JVM will start Signal Dispatcher thread to listen and handle
SIGBREAK(SIGQUIT)
signal.
|
|
signal_thread_entry
function handles the main logic.
|
|
Core algorithms in this function:
- use
os::signal_wait
to wait signals - Once
SIGQUIT(SIGBREAK)
signal recieved,Attach Listener
may be triggered (check if attach mechanism is opened, can be controlled by JVM argument-XX:+|-DisableAttachMechanism
) - function
AttachListener::is_init_trigger()
is called whencur_state == AL_NOT_INITIALIZED
In this function,1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
// 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]; int ret; struct stat64 st; sprintf(fn, ".attach_pid%d", os::current_process_id()); 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 non-root user creates the file if (os::Posix::matches_effective_uid_or_root(st.st_uid)) { init(); log_trace(attach)("Attach triggered by %s", fn); return true; } else { log_debug(attach)("File %s has wrong user id %d (vs %d). Attach is not triggered", fn, st.st_uid, geteuid()); } } return false; }
init
function will be called if file.Attach_pid<PID>
exists anduid
of this file is correct. - Attach Listener thread will be created and started in
init
function1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
// services/attachListener.cpp // Starts the Attach Listener thread void AttachListener::init() { EXCEPTION_MARK; const char thread_name[] = "Attach Listener"; Handle string = java_lang_String::create_from_str(thread_name, THREAD); if (has_init_error(THREAD)) { set_state(AL_NOT_INITIALIZED); return; } // Initialize thread_oop to put it into the system threadGroup Handle thread_group (THREAD, Universe::system_thread_group()); Handle thread_oop = JavaCalls::construct_new_instance(SystemDictionary::Thread_klass(), vmSymbols::threadgroup_string_void_signature(), thread_group, string, THREAD); if (has_init_error(THREAD)) { set_state(AL_NOT_INITIALIZED); 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); } }
What will “Attach Listener” thread do
We can see attach_listener_thread_entry
function is referred when create
Attach Listener thread, let’s see what will it do.
|
|
In this function, sevaral things will be done:
pd_init
function will be called, which will callLinuxAttachListener::init()
function to create a stream socket and bind to file/tmp/.java_pid<PID>
, and start tolisten
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
// os/linux/attachListener_linux.cpp 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 if (!_atexit_registered) { _atexit_registered = true; ::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; memset((void *)&addr, 0, sizeof(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) { // make sure the file is owned by the effective user and effective group // e.g. the group could be inherited from the directory in case the s bit is set RESTARTABLE(::chown(initial_path, geteuid(), getegid()), 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; }
- If succeeded, call
AttachListener::set_initialized()
function to set state toAL_INITIALIZED
- In non-stopping loop, keep dequeuing
option
s byLinuxAttachListener::dequeue()
function, then call the function belongs to the dequeuedoption
; the subscribed functions are:1 2 3 4 5 6 7 8 9 10 11 12 13
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 } };
IPC protocal during communication
Finally, let’s have a look at the protocal during IPC communication, which lies
in the dequeue
process we talked before.
LinuxAttachListener::dequeue()
function will be called in the dequeue
process.
|
|
We can find the protocal part in LinuxAttachListener::read_request
function.
|
|
Conclusion
That’s the entire interactive process of the Java Attach Mechanism. After walking through the source code, we have a better understanding of this mechanism, which is widely used by many tools like JStack, JPS, Async-Profiler, etc.
We can see the core method to enable Attach is Unix Domain Socket, JVM subcribes some basic functions, and through a declaired protocal, some other process may call these functions to dump threads, dump heaps, etc.
In addition, rather than launch the socket server on starting, JVM decide to
launch the server when several requirements satisfied, including SIGQUIT
signal recieved, .Attach_pid<PID>
file found, etc.
References
Author Wenfeng
LastMod 2019-11-11