1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        JVM中的監(jiān)聽信號的線程以及Unix域套接字通信的線程

        共 5122字,需瀏覽 11分鐘

         ·

        2021-09-13 08:01

        【實驗】

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

        以上代碼中,讓JVM不退出,我們對它做點手腳,看一下JVM中的兩個線程.


        編譯之后運行它.


        通過jps查看進程號=6617



        查看進程6617的線程
        ps -Lf 6617



        共計20個輕量級進程(LWP),即線程.

        也可以通過/proc/6617/task查看進程6617下有多少個任務(wù)(即線程), 也是20個線程,如下.



        我們再看一下這個進程6617打開的文件描述符,如下
        ls -l /proc/6617/fd



        共計6個文件描述符, 0,1,2分別是標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤輸出. 3,4,5描述符表示打開的3個jar.

        總結(jié)一下,此時的JVM里面,共計20個線程,進程打開了6個文件描述符.

        面試題: 如何知道JVM中的線程個數(shù),有哪些方法?


        接下來,我們在/tmp目錄下創(chuàng)建一個.attach_pid6617文件,如下




        接下來,我們使用kill命令向進程發(fā)送退出信號.




        說明: 信號機制是進程間通信的一種方式


        再觀察下線程的信息





        多了一個6666的線程.

        再看下進程6617打開的文件描述符



        會發(fā)現(xiàn)多了一個文件描述6,而且還是個socket文件描述符.

        總結(jié)一下,使用kill命令向JVM進程發(fā)送一個退出信號, 結(jié)果JVM多了1個線程,還多了1個sokcet文件描述符.


        進程間通信的方式有很多,其中信號就是其中一種方式. 關(guān)于進程間的通信可以閱讀它[ https://www.yuque.com/infuq/language/rvdvcu ] . 向JVM發(fā)送一個信號之后,那么JVM必然有一個線程來處理信號,而這個線程就是Signal Dispatcher線程.  

        我相信,讀者朋友,通過jstack命令查看線程棧的時候,一定能看到這個線程.


        Signal Dispatcher線程在JVM啟動的時候就創(chuàng)建了. 關(guān)于JVM的啟動,我們先在這里簡單說一下.

        在jdk/src/share/bin/main.c文件中,有個main方法,它是一切的源頭,JVM就是從這里開始它的人生之旅的,經(jīng)過一路小跑,會創(chuàng)建main線程,也會創(chuàng)建JVM. 還會創(chuàng)建Signal Dispatcher線程,Signal Dispatcher線程會阻塞等待接收外部的信號. 比如上文中,我們使用kill向指定的進程6617發(fā)送的3號退出信號,就是由進程6617中的Signal Dispatcher線程來處理的. Signal Dispatcher線程在收到并處理3號退出信號的時候,它會創(chuàng)建Attach Listener線程,也會創(chuàng)建一個socket文件描述符,這個socket文件描述符就是上文中看到的那個6號文件描述符,那么這個socket文件描述符能干啥用呢?

        除了信號可以用于進程間通信, Unix Domain Socket也可以用于進程間通信.  這種socket有別于網(wǎng)絡(luò)socket.  Unix Domain Socket僅用于本地進程間通信, 而網(wǎng)絡(luò)socket用于網(wǎng)絡(luò)間的進程間通信. 而通過Unix Domain Socket創(chuàng)建出來的6號文件描述符,它就是由Attach Listener這個線程來使用的. 這個Attach Listener線程作為服務(wù)端,監(jiān)聽客戶端的請求. 比如像jstack命令,阿里的Arthas(阿爾薩斯)等工具,它們底層都是通過這個socket文件描述符連接到目標(biāo)JVM,從而實現(xiàn)通信. 




        我們通過JDK自帶的bin目錄下的工具jvisualvm,通過圖形化的方式,再次查看下進程6617中的線程.





        看看你公司的服務(wù)器是否有這兩個線程呢?

        接下來我們通過3種方式獲取進程6617的線程棧信息.


        面試題: 如何得到一個進程的線程棧信息?


        第一種方式就是通過jstack命令,或者JDK體系的其他命令.



        第二種方式,通過Java代碼的方式


        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 { // attach底層就是發(fā)送了一個kill -3 6617的命令給目標(biāo)JVM VirtualMachine virtualMachine = VirtualMachine.attach("6617"); HotSpotVirtualMachine hotSpotVirtualMachine = (HotSpotVirtualMachine)virtualMachine; // 發(fā)送threaddump命令給目標(biāo)JVM InputStream inputStream = hotSpotVirtualMachine.remoteDataDump(new String[]{});
        byte[] buff = new byte[256]; int len; do { // 接收目標(biāo)JVM返回的數(shù)據(jù) 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(); }}


        編譯并運行這個Java程序,依然可以得到進程6617的線程棧信息




        第三種方式,通過C語言的方式, 之所以通過C語言的方式,旨在說明一點,不管我們使用的是jstack命令,還是上面的Java程序,或者阿里開源的Arthas(阿爾薩斯)工具,在它們的底層,都是通過同一種方式與目標(biāo)JVM進行通信的, 而通過C語言,能更好的把它展現(xiàn)給我們看.


        個人理解: 如果真想把JVM或者JDK學(xué)透了,C語言是要熟悉的. JVM的底層都是C語言,包括與操作系統(tǒng)的一些交互,都是C語言. 包括進程間的通信等, 如果不懂C語言,不懂一些操作系統(tǒng)的知識,那么很難學(xué)透JVM或者JDK. 之所以要學(xué)習(xí)JVM等底層知識, 個人理解,主要是讓我們的知識體系健全,不至于知識碎片化.


        代碼如下


        // 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 8192const 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"; // 長度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));
        // 方式一 send(fd, cmd, 16, 0); recv(fd, buffer, BUFFER_SIZE, 0);
        // 方式二 //write(fd, cmd, 16); //read(fd, buffer, BUFFER_SIZE);
        printf("\n%s\n", buffer); close(fd); return 0;}


        編譯



        運行



        上面我們可以看到,線程棧信息正常打印出來了. 那么它是如何做到的呢?
        首先,在代碼中定義了一個 const char *filename = "/tmp/.java_pid6617"; 文件名, 我們看下這個文件.



        6617就是進程ID. 當(dāng)我們通過kill命令向JVM發(fā)送3號退出信號的時候, Signal Dispatcher線程就會把Attach Listener線程創(chuàng)建出來, Attach Listener線程就會根據(jù)進程ID創(chuàng)建一個/tmp/.java_pid<PID>的文件. 如果是網(wǎng)絡(luò)socket通信,是基于IP和端口,而如果是Unix Domain Socket通信,就是基于文件的,而此時創(chuàng)建了一個/tmp/.java_pid<PID>的文件, Attach Listener線程就會創(chuàng)建一個服務(wù)端的socket, 那么客戶端就可以根據(jù)這個/tmp/.java_pid<PID>的文件創(chuàng)建一個客戶端,然后與服務(wù)端進行通信了. 那么如何創(chuàng)建客戶端的socket呢?


        在我們的C語言代碼里


        // 創(chuàng)建Unix Domain Socket用于本機進程間通信fd = socket(PF_UNIX, SOCK_STREAM, 0);// 連接服務(wù)器. 服務(wù)器也是通過Unix Domain Socket創(chuàng)建的.connect(fd, (struct sockaddr *) &un, sizeof(un));


        通過以上兩句,創(chuàng)建了客戶端的socket, 并與服務(wù)端(也就是目標(biāo)JVM)建立了連接, 然后就是發(fā)送命令了.
        代碼中我們發(fā)送了一個threaddump的命令,如下

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


        一切皆協(xié)議, 客戶端和服務(wù)端約定好了, 服務(wù)端接收什么樣子的命令格式才表示需要dump線程棧, 于是乎,客戶端就構(gòu)造這樣的命令, 然后把它發(fā)送給目標(biāo)JVM.  目標(biāo)JVM的Attach Listener線程收到命令之后,進行處理,然后把處理結(jié)果返回給客戶端, 于是乎客戶端就拿到了目標(biāo)JVM的線程棧.




        本篇啰嗦這么多,主要就是在表達,如何與目標(biāo)JVM進行通信,以及涉及的一些線程和知識點.

        瀏覽 37
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产真实交换夫妇视频 | 98国产日韩 | 一级肉体全黄裸片高潮不断 | 国产高清操逼 | 精品美女在线 | 柳神含精肉臀迎合娇吟 | 日本三级福利片 | 国产区精品豆花 | 少妇的肥美肉蚌一开一合 | 男男情×片|