免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
阿里不允許使用 Executors 創(chuàng)建線程池!那怎么使用,怎么監(jiān)控?


作者:小傅哥
博客:https://bugstack.cn

沉淀、分享、成長,讓自己和他人都能有所收獲!😄

一、前言

五常大米好吃!

哈哈哈,是不你總買五常大米,其實(shí)五常和榆樹是挨著的,榆樹大米也好吃,榆樹還是天下第一糧倉呢!但是五常出名,所以只認(rèn)識五常。

為什么提這個呢,因?yàn)榘⒗锊辉试S使用 Executors 創(chuàng)建線程池!其他很多大廠也不允許,這么創(chuàng)建的話,控制不好會出現(xiàn)OOM。

,本篇就帶你學(xué)習(xí)四種線程池的不同使用方式、業(yè)務(wù)場景應(yīng)用以及如何監(jiān)控線程。

二、面試題

謝飛機(jī),小記!,上次從面試官那逃跑后,惡補(bǔ)了多線程,自己好像也內(nèi)卷了,所以出門逛逛!

面試官:嗨,飛機(jī),飛機(jī),這邊!

謝飛機(jī):嗯?!哎呀,面試官你咋來南海子公園了?

面試官:我家就附近,跑步來了。最近你咋樣,上次問你的多線程學(xué)了嗎?

謝飛機(jī):哎,看了是看了,記不住鴨!

面試官:嗯,不常用確實(shí)記不住。不過你可以選擇跳槽,來大廠,大廠的業(yè)務(wù)體量較大!

謝飛機(jī):我就糾結(jié)呢,想回家考教師資格證了,我們村小學(xué)要教java了!

面試官:哈哈哈哈哈,一起!

三、四種線程池使用介紹

Executors 是創(chuàng)建線程池的工具類,比較典型常見的四種線程池包括:newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool、newScheduledThreadPool。每一種都有自己特定的典型例子,可以按照每種的特性用在不同的業(yè)務(wù)場景,也可以做為參照精細(xì)化創(chuàng)建線程池。

1. newFixedThreadPool

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    for (int i = 1; i < 5; i++) {
        int groupId = i;
        executorService.execute(() -> {
            for (int j = 1; j < 5; j++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ignored) {
                }
                logger.info("第 {} 組任務(wù),第 {} 次執(zhí)行完成", groupId, j);
            }
        });
    }
    executorService.shutdown();
}

// 測試結(jié)果
23:48:24.628 [pool-2-thread-1] INFO  o.i.i.test.Test_newFixedThreadPool -1 組任務(wù),第 1 次執(zhí)行完成
23:48:24.628 [pool-2-thread-2] INFO  o.i.i.test.Test_newFixedThreadPool -2 組任務(wù),第 1 次執(zhí)行完成
23:48:24.628 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool -3 組任務(wù),第 1 次執(zhí)行完成
23:48:25.633 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool -3 組任務(wù),第 2 次執(zhí)行完成
23:48:25.633 [pool-2-thread-1] INFO  o.i.i.test.Test_newFixedThreadPool -1 組任務(wù),第 2 次執(zhí)行完成
23:48:25.633 [pool-2-thread-2] INFO  o.i.i.test.Test_newFixedThreadPool -2 組任務(wù),第 2 次執(zhí)行完成
23:48:26.633 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool -3 組任務(wù),第 3 次執(zhí)行完成
23:48:26.633 [pool-2-thread-2] INFO  o.i.i.test.Test_newFixedThreadPool -2 組任務(wù),第 3 次執(zhí)行完成
23:48:26.633 [pool-2-thread-1] INFO  o.i.i.test.Test_newFixedThreadPool -1 組任務(wù),第 3 次執(zhí)行完成
23:48:27.634 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool -3 組任務(wù),第 4 次執(zhí)行完成
23:48:27.634 [pool-2-thread-2] INFO  o.i.i.test.Test_newFixedThreadPool -2 組任務(wù),第 4 次執(zhí)行完成
23:48:27.634 [pool-2-thread-1] INFO  o.i.i.test.Test_newFixedThreadPool -1 組任務(wù),第 4 次執(zhí)行完成
23:48:28.635 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool -4 組任務(wù),第 1 次執(zhí)行完成
23:48:29.635 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool -4 組任務(wù),第 2 次執(zhí)行完成
23:48:30.635 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool -4 組任務(wù),第 3 次執(zhí)行完成
23:48:31.636 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool -4 組任務(wù),第 4 次執(zhí)行完成

Process finished with exit code 0

圖解

  • 代碼new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
  • 介紹:創(chuàng)建一個固定大小可重復(fù)使用的線程池,以 LinkedBlockingQueue 無界阻塞隊(duì)列存放等待線程。
  • 風(fēng)險:隨著線程任務(wù)不能被執(zhí)行的的無限堆積,可能會導(dǎo)致OOM。

2. newSingleThreadExecutor

public static void main(String[] args) {
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    for (int i = 1; i < 5; i++) {
        int groupId = i;
        executorService.execute(() -> {
            for (int j = 1; j < 5; j++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ignored) {
                }
                logger.info("第 {} 組任務(wù),第 {} 次執(zhí)行完成", groupId, j);
            }
        });
    }
    executorService.shutdown();
}

// 測試結(jié)果
23:20:15.066 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -1 組任務(wù),第 1 次執(zhí)行完成
23:20:16.069 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -1 組任務(wù),第 2 次執(zhí)行完成
23:20:17.070 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -1 組任務(wù),第 3 次執(zhí)行完成
23:20:18.070 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -1 組任務(wù),第 4 次執(zhí)行完成
23:20:19.071 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -2 組任務(wù),第 1 次執(zhí)行完成
23:23:280.071 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -2 組任務(wù),第 2 次執(zhí)行完成
23:23:281.072 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -2 組任務(wù),第 3 次執(zhí)行完成
23:23:282.072 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -2 組任務(wù),第 4 次執(zhí)行完成
23:23:283.073 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -3 組任務(wù),第 1 次執(zhí)行完成
23:23:284.074 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -3 組任務(wù),第 2 次執(zhí)行完成
23:23:285.074 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -3 組任務(wù),第 3 次執(zhí)行完成
23:23:286.075 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -3 組任務(wù),第 4 次執(zhí)行完成
23:23:287.075 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -4 組任務(wù),第 1 次執(zhí)行完成
23:23:288.075 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -4 組任務(wù),第 2 次執(zhí)行完成
23:23:289.076 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -4 組任務(wù),第 3 次執(zhí)行完成
23:20:30.076 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor -4 組任務(wù),第 4 次執(zhí)行完成

圖解

  • 代碼new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
  • 介紹:只創(chuàng)建一個執(zhí)行線程任務(wù)的線程池,如果出現(xiàn)意外終止則再創(chuàng)建一個。
  • 風(fēng)險:同樣這也是一個無界隊(duì)列存放待執(zhí)行線程,無限堆積下會出現(xiàn)OOM。

3. newCachedThreadPool

public static void main(String[] args) throws InterruptedException {
    ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 1; i < 5; i++) {
        int groupId = i;
        executorService.execute(() -> {
            for (int j = 1; j < 5; j++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ignored) {
                }
                logger.info("第 {} 組任務(wù),第 {} 次執(zhí)行完成", groupId, j);
            }
        });
    }
    executorService.shutdown();
    
    // 測試結(jié)果
    23:25:59.818 [pool-2-thread-2] INFO  o.i.i.test.Test_newCachedThreadPool -2 組任務(wù),第 1 次執(zhí)行完成
    23:25:59.818 [pool-2-thread-3] INFO  o.i.i.test.Test_newCachedThreadPool -3 組任務(wù),第 1 次執(zhí)行完成
    23:25:59.818 [pool-2-thread-1] INFO  o.i.i.test.Test_newCachedThreadPool -1 組任務(wù),第 1 次執(zhí)行完成
    23:25:59.818 [pool-2-thread-4] INFO  o.i.i.test.Test_newCachedThreadPool -4 組任務(wù),第 1 次執(zhí)行完成
    23:25:00.823 [pool-2-thread-4] INFO  o.i.i.test.Test_newCachedThreadPool -4 組任務(wù),第 2 次執(zhí)行完成
    23:25:00.823 [pool-2-thread-1] INFO  o.i.i.test.Test_newCachedThreadPool -1 組任務(wù),第 2 次執(zhí)行完成
    23:25:00.823 [pool-2-thread-2] INFO  o.i.i.test.Test_newCachedThreadPool -2 組任務(wù),第 2 次執(zhí)行完成
    23:25:00.823 [pool-2-thread-3] INFO  o.i.i.test.Test_newCachedThreadPool -3 組任務(wù),第 2 次執(zhí)行完成
    23:25:01.823 [pool-2-thread-4] INFO  o.i.i.test.Test_newCachedThreadPool -4 組任務(wù),第 3 次執(zhí)行完成
    23:25:01.823 [pool-2-thread-1] INFO  o.i.i.test.Test_newCachedThreadPool -1 組任務(wù),第 3 次執(zhí)行完成
    23:25:01.824 [pool-2-thread-2] INFO  o.i.i.test.Test_newCachedThreadPool -2 組任務(wù),第 3 次執(zhí)行完成
    23:25:01.824 [pool-2-thread-3] INFO  o.i.i.test.Test_newCachedThreadPool -3 組任務(wù),第 3 次執(zhí)行完成
    23:25:02.824 [pool-2-thread-1] INFO  o.i.i.test.Test_newCachedThreadPool -1 組任務(wù),第 4 次執(zhí)行完成
    23:25:02.824 [pool-2-thread-4] INFO  o.i.i.test.Test_newCachedThreadPool -4 組任務(wù),第 4 次執(zhí)行完成
    23:25:02.825 [pool-2-thread-3] INFO  o.i.i.test.Test_newCachedThreadPool -3 組任務(wù),第 4 次執(zhí)行完成
    23:25:02.825 [pool-2-thread-2] INFO  o.i.i.test.Test_newCachedThreadPool -2 組任務(wù),第 4 次執(zhí)行完成
}

圖解

  • 代碼new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>())
  • 介紹:首先 SynchronousQueue 是一個生產(chǎn)消費(fèi)模式的阻塞任務(wù)隊(duì)列,只要有任務(wù)就需要有線程執(zhí)行,線程池中的線程可以重復(fù)使用。
  • 風(fēng)險:如果線程任務(wù)比較耗時,又大量創(chuàng)建,會導(dǎo)致OOM

4. newScheduledThreadPool

public static void main(String[] args) {
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    executorService.schedule(() -> {
        logger.info("3秒后開始執(zhí)行");
    }, 3, TimeUnit.SECONDS);
    executorService.scheduleAtFixedRate(() -> {
        logger.info("3秒后開始執(zhí)行,以后每2秒執(zhí)行一次");
    }, 3, 2, TimeUnit.SECONDS);
    executorService.scheduleWithFixedDelay(() -> {
        logger.info("3秒后開始執(zhí)行,后續(xù)延遲2秒");
    }, 3, 2, TimeUnit.SECONDS);
}

// 測試結(jié)果
23:28:32.442 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3秒后開始執(zhí)行
23:28:32.444 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3秒后開始執(zhí)行,以后每2秒執(zhí)行一次
23:28:32.444 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3秒后開始執(zhí)行,后續(xù)延遲223:28:34.441 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3秒后開始執(zhí)行,以后每2秒執(zhí)行一次
23:28:34.445 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3秒后開始執(zhí)行,后續(xù)延遲223:28:36.440 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3秒后開始執(zhí)行,以后每2秒執(zhí)行一次
23:28:36.445 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3秒后開始執(zhí)行,后續(xù)延遲2

圖解

  • 代碼public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue()); }
  • 介紹:這就是一個比較有意思的線程池了,它可以延遲定時執(zhí)行,有點(diǎn)像我們的定時任務(wù)。同樣它也是一個無限大小的線程池 Integer.MAX_VALUE。它提供的調(diào)用方法比較多,包括:scheduleAtFixedRatescheduleWithFixedDelay,可以按需選擇延遲執(zhí)行方式。
  • 風(fēng)險:同樣由于這是一組無限容量的線程池,所以依舊又OOM風(fēng)險。

四、線程池使用場景說明

什么時候使用線程池?

說簡單是當(dāng)為了給老板省錢的時候,因?yàn)槭褂镁€程池可以降低服務(wù)器資源的投入,讓每臺機(jī)器盡可能更大限度的使用CPU。

😄那你這么說肯定沒辦法升職加薪了!

所以如果說的高大上一點(diǎn),那么是在符合科特爾法則阿姆達(dá)爾定律 的情況下,引入線程池的使用最為合理。啥意思呢,還得簡單說!

假如:我們有一套電商服務(wù),用戶瀏覽商品的并發(fā)訪問速率是:1000客戶/每分鐘,平均每個客戶在服務(wù)器上的耗時0.5分鐘。根據(jù)利特爾法則,在任何時刻,服務(wù)端都承擔(dān)著1000*0.5=500個客戶的業(yè)務(wù)處理量。過段時間大促了,并發(fā)訪問的用戶擴(kuò)了一倍2000客戶了,那怎么保障服務(wù)性能呢?

  1. 提高服務(wù)器并發(fā)處理的業(yè)務(wù)量,即提高到2000×0.5=1000
  2. 減少服務(wù)器平均處理客戶請求的時間,即減少到:2000×0.25=500

所以:在有些場景下會把串行的請求接口,壓縮成并行執(zhí)行,如圖 22-5

但是,線程池的使用會隨著業(yè)務(wù)場景變化而不同,如果你的業(yè)務(wù)需要大量的使用線程池,并非常依賴線程池,那么就不可能用 Executors 工具類中提供的方法。因?yàn)檫@些線程池的創(chuàng)建都不夠精細(xì)化,也非常容易造成OOM風(fēng)險,而且隨著業(yè)務(wù)場景邏輯不同,會有IO密集型和CPU密集型。

最終,大家使用的線程池都是使用 new ThreadPoolExecutor() 創(chuàng)建的,當(dāng)然也有基于Spring的線程池配置 org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor。

可你想過嗎,同樣一個接口在有活動時候怎么辦、有大促時候怎么辦,可能你當(dāng)時設(shè)置的線程池是合理的,但是一到流量非常大的時候就很不適合了,所以如果能動態(tài)調(diào)整線程池就非常有必要了。而且使用 new ThreadPoolExecutor() 方式創(chuàng)建的線程池是可以通過提供的 set 方法進(jìn)行動態(tài)調(diào)整的。有了這個動態(tài)調(diào)整的方法后,就可以把線程池包裝起來,在配合動態(tài)調(diào)整的頁面,動態(tài)更新線程池參數(shù),就可以非常方便的調(diào)整線程池了。

五、獲取線程池監(jiān)控信息

你收過報警短信嗎?

收過,半夜還有報警機(jī)器人打電話呢!崴,你的系統(tǒng)有個機(jī)器睡著了,快起來看看!!!

所以,如果你高頻、高依賴線程池,那么有一個完整的監(jiān)控系統(tǒng),就非重要了??偛荒芫€上掛了,你還不知道!

可監(jiān)控內(nèi)容

方法含義
getActiveCount()線程池中正在執(zhí)行任務(wù)的線程數(shù)量
getCompletedTaskCount()線程池已完成的任務(wù)數(shù)量,該值小于等于taskCount
getCorePoolSize()線程池的核心線程數(shù)量
getLargestPoolSize()線程池曾經(jīng)創(chuàng)建過的最大線程數(shù)量。通過這個數(shù)據(jù)可以知道線程池是否滿過,也就是達(dá)到了maximumPoolSize
getMaximumPoolSize()線程池的最大線程數(shù)量
getPoolSize()線程池當(dāng)前的線程數(shù)量
getTaskCount()線程池已經(jīng)執(zhí)行的和未執(zhí)行的任務(wù)總數(shù)

1. 重寫線程池方式監(jiān)控

如果我們想監(jiān)控一個線程池的方法執(zhí)行動作,最簡單的方式就是繼承這個類,重寫方法,在方法中添加動作收集信息。

偽代碼

public class ThreadPoolMonitor extends ThreadPoolExecutor {

    @Override
    public void shutdown() {
        // 統(tǒng)計(jì)已執(zhí)行任務(wù)、正在執(zhí)行任務(wù)、未執(zhí)行任務(wù)數(shù)量
        super.shutdown();
    }

    @Override
    public List<Runnable> shutdownNow() {
        // 統(tǒng)計(jì)已執(zhí)行任務(wù)、正在執(zhí)行任務(wù)、未執(zhí)行任務(wù)數(shù)量
        return super.shutdownNow();
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        // 記錄開始時間
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        // 記錄完成耗時
    }
    
    ...
}

2. 基于IVMTI方式監(jiān)控

這塊是監(jiān)控的重點(diǎn),因?yàn)槲覀儾惶赡茏屆恳粋€需要監(jiān)控的線程池都來重寫的方式記錄,這樣的改造成本太高了。

那么除了這個笨方法外,可以選擇使用基于JVMTI的方式,進(jìn)行開發(fā)監(jiān)控組件。

JVMTI:JVMTI(JVM Tool Interface)位于jpda最底層,是Java虛擬機(jī)所提供的native編程接口。JVMTI可以提供性能分析、debug、內(nèi)存管理、線程分析等功能。

基于jvmti提供的接口服務(wù),運(yùn)用C++代碼(win32-add_library)在Agent_OnLoad里開發(fā)監(jiān)控服務(wù),并生成dll文件。開發(fā)完成后在java代碼中加入agentpath,這樣就可以監(jiān)控到我們需要的信息內(nèi)容。

環(huán)境準(zhǔn)備

  1. Dev-C++
  2. JetBrains CLion 2018.2.3
  3. IntelliJ IDEA Community Edition 2018.3.1 x64
  4. jdk1.8.0_45 64位
  5. jvmti(在jdk安裝目錄下jdk1.8.0_45\include里,把include整個文件夾復(fù)制到和工程案例同層級目錄下,便于 include 引用)

配置信息:(路徑相關(guān)修改為自己的)

  1. C++開發(fā)工具Clion配置
    1.配置位置;Settings->Build,Execution,Deployment->Toolchains
    1. MinGM配置:D:\Program Files (x86)\Dev-Cpp\MinGW64
  2. java調(diào)試時配置
    1. 配置位置:Run/Debug Configurations ->VM options
    2. 配置內(nèi)容:-agentpath:E:\itstack\git\github.com\itstack-jvmti\cmake-build-debug\libitstack_jvmti.dll

2.1 先做一個監(jiān)控例子

Java工程

public class TestLocationException {

    public static void main(String[] args) {
        Logger logger = Logger.getLogger("TestLocationException");
        try {
            PartnerEggResourceImpl resource = new PartnerEggResourceImpl();
            Object obj = resource.queryUserInfoById(null);
            logger.info("測試結(jié)果:" + obj);
        } catch (Exception e) {
            //屏蔽異常
 }
    }
}

class PartnerEggResourceImpl {
    Logger logger = Logger.getLogger("PartnerEggResourceImpl");
    public Object queryUserInfoById(String userId) {
        logger.info("根據(jù)用戶Id獲取用戶信息" + userId);
        if (null == userId) {
            throw new NullPointerException("根據(jù)用戶Id獲取用戶信息,空指針異常");
        }
        return userId;
    }
}

c++監(jiān)控

#include <iostream>
#include <cstring>
#include "jvmti.h"

using namespace std;

//異?;卣{(diào)函數(shù)
static void JNICALL
callbackException(jvmtiEnv *jvmti_env, JNIEnv *env, jthread thr, jmethodID methodId, jlocation location,
jobject exception, jmethodID catch_method, jlocation catch_location) {
// 獲得方法對應(yīng)的類
jclass clazz;
jvmti_env->GetMethodDeclaringClass(methodId, &clazz);

// 獲得類的簽名
char *class_signature;
jvmti_env->GetClassSignature(clazz, &class_signature, nullptr);

//過濾非本工程類信息
string::size_type idx;
string class_signature_str = class_signature;
idx = class_signature_str.find("org/itstack");
if (idx != 1) {
return;
}

//異常類名稱
char *exception_class_name;
jclass exception_class = env->GetObjectClass(exception);
jvmti_env->GetClassSignature(exception_class, &exception_class_name, nullptr);

// 獲得方法名稱
char *method_name_ptr, *method_signature_ptr;
jvmti_env->GetMethodName(methodId, &method_name_ptr, &method_signature_ptr, nullptr);

//獲取目標(biāo)方法的起止地址和結(jié)束地址
jlocation start_location_ptr;    //方法的起始位置
jlocation end_location_ptr;      //用于方法的結(jié)束位置
jvmti_env->GetMethodLocation(methodId, &start_location_ptr, &end_location_ptr);

//輸出測試結(jié)果
cout << "測試結(jié)果 - 定位類的簽名:" << class_signature << endl;
cout << "測試結(jié)果 - 定位方法信息:" << method_name_ptr << " -> " << method_signature_ptr << endl;
cout << "測試結(jié)果 - 定位方法位置:" << start_location_ptr << " -> " << end_location_ptr + 1 << endl;
cout << "測試結(jié)果 - 異常類的名稱:" << exception_class_name << endl;

cout << "測試結(jié)果-輸出異常信息(可以分析行號):" << endl;
jclass throwable_class = (*env).FindClass("java/lang/Throwable");
jmethodID print_method = (*env).GetMethodID(throwable_class, "printStackTrace", "()V");
(*env).CallVoidMethod(exception, print_method);

}


JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
    jvmtiEnv *gb_jvmti = nullptr;
    //初始化
    vm->GetEnv(reinterpret_cast<void **>(&gb_jvmti), JVMTI_VERSION_1_0);
    // 創(chuàng)建一個新的環(huán)境
    jvmtiCapabilities caps;
    memset(&caps, 0, sizeof(caps));
    caps.can_signal_thread = 1;
    caps.can_get_owned_monitor_info = 1;
    caps.can_generate_method_entry_events = 1;
    caps.can_generate_exception_events = 1;
    caps.can_generate_vm_object_alloc_events = 1;
    caps.can_tag_objects = 1;
    // 設(shè)置當(dāng)前環(huán)境
    gb_jvmti->AddCapabilities(&caps);
    // 創(chuàng)建一個新的回調(diào)函數(shù)
    jvmtiEventCallbacks callbacks;
    memset(&callbacks, 0, sizeof(callbacks));
    //異常回調(diào)
    callbacks.Exception = &callbackException;
    // 設(shè)置回調(diào)函數(shù)
    gb_jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
    // 開啟事件監(jiān)聽(JVMTI_EVENT_EXCEPTION)
    gb_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, nullptr);
    return JNI_OK;
}

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) {
}

測試結(jié)果

在 VM vptions 中配置:-agentpath:E:\itstack\git\github.com\itstack-jvmti\cmake-build-debug\libitstack_jvmti.dll

十二月 16, 2020 23:53:27 下午 org.itstack.demo.PartnerEggResourceImpl queryUserInfoById
信息: 根據(jù)用戶Id獲取用戶信息null
java.lang.NullPointerException: 根據(jù)用戶Id獲取用戶信息,空指針異常
at org.itstack.demo.PartnerEggResourceImpl.queryUserInfoById(TestLocationException.java:26)
at org.itstack.demo.TestLocationException.main(TestLocationException.java:13)
測試結(jié)果-定位類的簽名:Lorg/itstack/demo/PartnerEggResourceImpl;
測試結(jié)果-定位方法信息:queryUserInfoById -> (Ljava/lang/String;)Ljava/lang/Object;
測試結(jié)果-定位方法位置:0 -> 43
測試結(jié)果-異常類的名稱:Ljava/lang/NullPointerException;
測試結(jié)果-輸出異常信息(可以分析行號)
  • 這就是基于JVMTI的方式進(jìn)行監(jiān)控,這樣的方式可以做到非入侵代碼。不需要硬編碼,也就節(jié)省了人力,否則所有人都會進(jìn)行開發(fā)監(jiān)控內(nèi)容,而這部分內(nèi)容與業(yè)務(wù)邏輯并無關(guān)系。

2.2 擴(kuò)展線程監(jiān)控

其實(shí)方法差不多,都是基于C++開發(fā)DLL文件,引入使用。不過這部分代碼會監(jiān)控方法信息,并采集線程的執(zhí)行內(nèi)容。

static void JNICALL callbackMethodEntry(jvmtiEnv *jvmti_env, JNIEnv *env, jthread thr, jmethodID method) {
    // 獲得方法對應(yīng)的類
    jclass clazz;
    jvmti_env->GetMethodDeclaringClass(method, &clazz);

    // 獲得類的簽名
    char *class_signature;
    jvmti_env->GetClassSignature(clazz, &class_signature, nullptr);

    //過濾非本工程類信息
    string::size_type idx;
    string class_signature_str = class_signature;
    idx = class_signature_str.find("org/itstack");

    gb_jvmti->RawMonitorEnter(gb_lock);

    {
        //must be deallocate
        char *name = NULL, *sig = NULL, *gsig = NULL;
        jint thr_hash_code = 0;

        error = gb_jvmti->GetMethodName(method, &name, &sig, &gsig);
        error = gb_jvmti->GetObjectHashCode(thr, &thr_hash_code);

        if (strcmp(name, "start") == 0 || strcmp(name, "interrupt") == 0 ||
            strcmp(name, "join") == 0 || strcmp(name, "stop") == 0 ||
            strcmp(name, "suspend") == 0 || strcmp(name, "resume") == 0) {

            //must be deallocate
            jobject thd_ptr = NULL;
            jint hash_code = 0;
            gb_jvmti->GetLocalObject(thr, 0, 0, &thd_ptr);
            gb_jvmti->GetObjectHashCode(thd_ptr, &hash_code);

            printf("[線程監(jiān)控]: thread (%10d) %10s (%10d)\n", thr_hash_code, name, hash_code);
        }
    }

    gb_jvmti->RawMonitorExit(gb_lock);
}

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {

    // 初始化
    jvm->GetEnv((void **) &gb_jvmti, JVMTI_VERSION_1_0);
    // 創(chuàng)建一個新的環(huán)境
    memset(&gb_capa, 0, sizeof(jvmtiCapabilities));
    gb_capa.can_signal_thread = 1;
    gb_capa.can_get_owned_monitor_info = 1;
    gb_capa.can_generate_method_exit_events = 1;
    gb_capa.can_generate_method_entry_events = 1;
    gb_capa.can_generate_exception_events = 1;
    gb_capa.can_generate_vm_object_alloc_events = 1;
    gb_capa.can_tag_objects = 1;
    gb_capa.can_generate_all_class_hook_events = 1;
    gb_capa.can_generate_native_method_bind_events = 1;
    gb_capa.can_access_local_variables = 1;
    gb_capa.can_get_monitor_info = 1;
    // 設(shè)置當(dāng)前環(huán)境
    gb_jvmti->AddCapabilities(&gb_capa);
    // 創(chuàng)建一個新的回調(diào)函數(shù)
    jvmtiEventCallbacks callbacks;
    memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
    // 方法回調(diào)
    callbacks.MethodEntry = &callbackMethodEntry;
    // 設(shè)置回調(diào)函數(shù)
    gb_jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));

    gb_jvmti->CreateRawMonitor("XFG", &gb_lock);

    // 注冊事件監(jiān)聽(JVMTI_EVENT_VM_INIT、JVMTI_EVENT_EXCEPTION、JVMTI_EVENT_NATIVE_METHOD_BIND、JVMTI_EVENT_CLASS_FILE_LOAD_HOOK、JVMTI_EVENT_METHOD_ENTRY、JVMTI_EVENT_METHOD_EXIT)
    error = gb_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, (jthread) NULL);
    error = gb_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, (jthread) NULL);
    error = gb_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_NATIVE_METHOD_BIND, (jthread) NULL);
    error = gb_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, (jthread) NULL);
    error = gb_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, (jthread) NULL);
    error = gb_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, (jthread) NULL);

    return JNI_OK;
}
  • 從監(jiān)控的代碼可以看到,這里有線程的 start、stop、join、interrupt 等,并可以記錄執(zhí)行信息。
  • 另外這里監(jiān)控的方法執(zhí)行回調(diào),SetEventCallbacks(&callbacks, sizeof(callbacks)); 以及相應(yīng)事件的添加。

六、總結(jié)

  • 如果說你所經(jīng)歷的業(yè)務(wù)體量很小,那么幾乎并不需要如此復(fù)雜的技術(shù)棧深度學(xué)習(xí),甚至幾乎不需要擴(kuò)展各類功能,也不需要監(jiān)控。但終究有一些需要造飛機(jī)的大廠,他們的業(yè)務(wù)體量龐大,并發(fā)數(shù)高,讓原本可能就是一個簡單的查詢接口,也要做熔斷、降級、限流、緩存、線程、異步、預(yù)熱等等操作。
  • 知其然才敢用,如果對一個技術(shù)點(diǎn)不是太熟悉,就不要胡亂使用,否則遇到的OOM并不是那么好復(fù)現(xiàn),尤其是在并發(fā)場景下。當(dāng)然如果你們技術(shù)體系中有各種服務(wù),比如流量復(fù)現(xiàn)、鏈路追蹤等等,那么還好。
  • 又扯到了這,一個堅(jiān)持學(xué)習(xí)、分享、沉淀的男人!好了,如果有錯字、內(nèi)容不準(zhǔn)確,歡迎直接懟給我,我喜歡接受。但不要欺負(fù)我哦哈哈哈哈哈!

七、系列推薦

  • Thread.start() ,它是怎么讓線程啟動的呢?
  • Thread 線程,狀態(tài)轉(zhuǎn)換、方法使用、原理分析
  • 手寫線程池,對照學(xué)習(xí)ThreadPoolExecutor線程池實(shí)現(xiàn)原理!
  • ReentrantLock之AQS原理分析和實(shí)踐使用
  • 如果你只寫CRUD,那這種技術(shù)你永遠(yuǎn)碰不到
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
初學(xué)者第68節(jié)多線程之線程池(十一)
java線程池
java自帶線程池和隊(duì)列詳細(xì)講解
四種Java線程池用法解析
課堂筆記第八周
ExecutorService 關(guān)閉 and 如何判斷線程池中任務(wù)執(zhí)行完畢
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服