Threads listening for signals in the JVM and Unix domain socket communication threads

Posted by randomfool on Sat, 20 Nov 2021 11:23:05 +0100

[experiment]

package com.infuq.tmp;

public class Main {
    public static void main(String args[]) {
        for (;;) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

In the above code, let the JVM not exit. Let's do something about it and take a look at the two threads in the JVM

Run it after compiling

View process number through jps = 6617

View threads of process 6617
ps -Lf 6617

A total of 20 lightweight processes (LWP), i.e. threads

You can also check the number of tasks (i.e. threads) under process 6617 through / proc/6617/task, which is also 20 threads, as shown below

Let's take another look at the file descriptor opened by the process 6617, as follows

ls -l /proc/6617/fd

There are 6 file descriptors in total. 0, 1 and 2 are standard input, standard output and standard error output respectively. 3, 4 and 5 descriptors represent the three open jar s

To sum up, there are 20 threads in the JVM at this time, and the process has opened 6 file descriptors

Interview question: how to know the number of threads in the JVM and what are the methods?

Next, we create an. Attach in the / tmp directory_ Pid6617 file, as follows

Next, we use the kill command to send an exit signal to the process

Signaling mechanism is a way of inter process communication

Then observe the thread information

There is an extra thread of 6666

Then look at the file descriptor opened by process 6617

You will find an additional file description 6, which is also a socket file descriptor

To sum up, use the kill command to send an exit signal to the JVM process. As a result, the JVM has one more thread and one more sokcet file descriptor

There are many ways of inter process communication, among which signal is one of them Communication between processes can read it After sending a signal to the JVM, the JVM must have a thread to process the signal, and this thread is the Signal Dispatcher thread. I believe readers can see this thread when viewing the thread stack through the jstack command

The Signal Dispatcher thread is created when the JVM starts. Let's briefly talk about the startup of the JVM

In the jdk/src/share/bin/main.c file, there is a main method, which is the source of everything. The JVM starts its life journey from here. After a trot, it will create both a main thread and a JVM. It will also create a Signal Dispatcher thread, which will block waiting to receive external signals. For example, as mentioned above, The No. 3 exit signal sent by kill to the specified process 6617 is processed by the Signal Dispatcher thread in the process 6617. When the Signal Dispatcher thread receives and processes the No. 3 exit signal, it will create an Attach Listener thread and a socket file descriptor. The socket file descriptor is the No. 6 file descriptor seen above, So what does this socket file descriptor do?

In addition to signals, Unix Domain Socket can also be used for interprocess communication. This socket is different from network socket. Unix Domain Socket is only used for local interprocess communication, while network socket is used for interprocess communication between networks. The No. 6 file descriptor created through Unix Domain Socket, It is used by the Attach Listener thread. The Attach Listener thread acts as a server and listens to client requests. For example, tools such as jstack command and Ali's Arthas are connected to the target JVM through the socket file descriptor to realize communication

Through the tool jvisualvm in the bin directory of JDK, we can check the threads in process 6617 again in a graphical way


See if your company's server has these two threads?

Next, we get the thread stack information of process 6617 in three ways

Interview question: how to get the thread stack information of a process?

The first way is through jstack command or other commands of JDK system

The second way is through Java code

import com.sun.tools.attach.VirtualMachine;
import sun.tools.attach.HotSpotVirtualMachine;
import java.io.InputStream;

public class Attach {
    public static void main(String[] args)throws Exception {
        // The bottom layer of attach sends a kill -3 6617 command to the target JVM
        VirtualMachine virtualMachine = VirtualMachine.attach("6617");
        HotSpotVirtualMachine hotSpotVirtualMachine = (HotSpotVirtualMachine)virtualMachine;
        // Send the threaddump command to the target JVM
        InputStream inputStream = hotSpotVirtualMachine.remoteDataDump(new String[]{});

        byte[] buff = new byte[256];
        int len;
        do {
        	// Receive the data returned by the target JVM
            len = inputStream.read(buff);
            if (len > 0) {
                String respone = new String(buff, 0, len, "UTF-8");
                System.out.print(respone);
            }
        } while(len > 0);

        inputStream.close();
        virtualMachine.detach();
    }
}

Compile and run the Java program, you can still get the thread stack information of process 6617

The third way is through the C language. The reason why we use the C language is to illustrate that whether we use the jstack command, the above Java program, or Ali's open source Arthas tool, at their bottom, we communicate with the target JVM in the same way. Through the C language, we can better show it to us

Personally, if you really want to learn the JVM or JDK thoroughly, you should be familiar with the C language. The bottom layer of the JVM is the C language, including some interactions with the operating system. Including inter process communication. If you don't understand the C language and some operating system knowledge, it's difficult to learn the JVM or JDK thoroughly. The reason why you want to learn the bottom knowledge such as the JVM is personal understanding, The main thing is to make our knowledge system sound and avoid knowledge fragmentation

The code is as follows

// threaddump.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <errno.h>
#include <stddef.h>
#include <unistd.h>

#define BUFFER_SIZE 8192
const char *filename = "/tmp/.java_pid6617";

int main(int argc, char **argv)
{
        struct sockaddr_un  un;
        int             fd;
        char            buffer[BUFFER_SIZE];
        char            *cmd = "1\0threaddump\0\0\0\0"; // Length 16

        un.sun_family = AF_UNIX;
        strcpy(un.sun_path, filename);

        fd = socket(PF_UNIX, SOCK_STREAM, 0);
        connect(fd, (struct sockaddr *) &un, sizeof(un));

        // Mode 1
        send(fd, cmd, 16, 0);
        recv(fd, buffer, BUFFER_SIZE, 0);

        // Mode II
        //write(fd, cmd, 16);
        //read(fd, buffer, BUFFER_SIZE);

        printf("\n%s\n", buffer);
        close(fd);
        return 0;
}

compile
function

As we can see above, the thread stack information is printed normally. So how does it do it?
First, a const char *filename = "/ tmp/.java_pid6617" is defined in the code; File name, let's look at this file
6617 is the process ID. when we send the No. 3 exit signal to the JVM through the kill Command, the Signal Dispatcher thread will create the Attach Listener thread, and the Attach Listener thread will create a / TMP /. Java based on the process ID_ PID < PID > file. If it is network socket communication, it is based on IP and port. If it is Unix Domain Socket communication, it is based on file. At this time, a / TMP /. Java is created_ PID < PID >, the Attach Listener thread will create a socket on the server side, and the client can use this / TMP /. Java_ PID < PID > file creates a client, and then communicates with the server. So how to create the socket of the client?
In our C language code

// Create a Unix Domain Socket for native interprocess communication
fd = socket(PF_UNIX, SOCK_STREAM, 0);
// Connect to the server. The server is also created through Unix Domain Socket
connect(fd, (struct sockaddr *) &un, sizeof(un));

Through the above two sentences, the socket of the client is created, a connection is established with the server (that is, the target JVM), and then the command is sent
In the code, we send a threaddump command, as follows

char            *cmd = "1\0threaddump\0\0\0\0"; // Length 16

Everything depends on the protocol. The client and the server have agreed on what kind of command format the server receives to indicate that the dump thread stack is required. Therefore, the client constructs such a command and sends it to the target JVM. After receiving the command, the Attach Listener thread of the target JVM processes it, and then returns the processing result to the client, So the client gets the thread stack of the target JVM

This article is so verbose, mainly expressing how to communicate with the target JVM, as well as some threads and knowledge points involved

Personal site
Language bird

official account

Topics: Linux Unix Interview