乐趣区

关于java:阿里不允许使用-Executors-创建线程池那怎么使用怎么监控


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

积淀、分享、成长,让本人和别人都能有所播种!????

一、前言

五常大米好吃!

哈哈哈,是不你总买五常大米,其实五常和榆树是挨着的,榆树大米也好吃,榆树还是天下第一粮仓呢!然而五常闻名,所以只意识五常。

为什么提这个呢,因为阿里不容许应用 Executors 创立线程池!其余很多大厂也不容许,这么创立的话,管制不好会呈现 OOM。

,本篇就带你学习四种线程池的不同应用形式、业务场景利用以及如何监控线程。

二、面试题

谢飞机,小记!,上次从面试官那逃跑后,恶补了多线程,本人如同也内卷了,所以出门逛逛!

面试官:嗨,飞机,飞机,这边!

谢飞机:嗯?!哎呀,面试官你咋来南海子公园了?

面试官:我家就左近,跑步来了。最近你咋样,上次问你的多线程学了吗?

谢飞机:哎,看了是看了,记不住鸭!

面试官:嗯,不罕用的确记不住。不过你能够抉择跳槽,来大厂,大厂的业务体量较大!

谢飞机:我就纠结呢,想回家考老师资格证了,咱们村小学要教 java 了!

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

三、四种线程池应用介绍

Executors 是创立线程池的工具类,比拟典型常见的四种线程池包含:newFixedThreadPoolnewSingleThreadExecutornewCachedThreadPoolnewScheduledThreadPool。每一种都有本人特定的典型例子,能够依照每种的个性用在不同的业务场景,也能够做为参照精细化创立线程池。

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("第 {} 组工作,第 {} 次执行实现", groupId, j);
            }
        });
    }
    executorService.shutdown();}

// 测试后果
23:48:24.628 [pool-2-thread-1] INFO  o.i.i.test.Test_newFixedThreadPool - 第 1 组工作,第 1 次执行实现
23:48:24.628 [pool-2-thread-2] INFO  o.i.i.test.Test_newFixedThreadPool - 第 2 组工作,第 1 次执行实现
23:48:24.628 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool - 第 3 组工作,第 1 次执行实现
23:48:25.633 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool - 第 3 组工作,第 2 次执行实现
23:48:25.633 [pool-2-thread-1] INFO  o.i.i.test.Test_newFixedThreadPool - 第 1 组工作,第 2 次执行实现
23:48:25.633 [pool-2-thread-2] INFO  o.i.i.test.Test_newFixedThreadPool - 第 2 组工作,第 2 次执行实现
23:48:26.633 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool - 第 3 组工作,第 3 次执行实现
23:48:26.633 [pool-2-thread-2] INFO  o.i.i.test.Test_newFixedThreadPool - 第 2 组工作,第 3 次执行实现
23:48:26.633 [pool-2-thread-1] INFO  o.i.i.test.Test_newFixedThreadPool - 第 1 组工作,第 3 次执行实现
23:48:27.634 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool - 第 3 组工作,第 4 次执行实现
23:48:27.634 [pool-2-thread-2] INFO  o.i.i.test.Test_newFixedThreadPool - 第 2 组工作,第 4 次执行实现
23:48:27.634 [pool-2-thread-1] INFO  o.i.i.test.Test_newFixedThreadPool - 第 1 组工作,第 4 次执行实现
23:48:28.635 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool - 第 4 组工作,第 1 次执行实现
23:48:29.635 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool - 第 4 组工作,第 2 次执行实现
23:48:30.635 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool - 第 4 组工作,第 3 次执行实现
23:48:31.636 [pool-2-thread-3] INFO  o.i.i.test.Test_newFixedThreadPool - 第 4 组工作,第 4 次执行实现

Process finished with exit code 0

图解

  • 代码new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
  • 介绍:创立一个固定大小可重复使用的线程池,以 LinkedBlockingQueue 无界阻塞队列寄存期待线程。
  • 危险:随着线程工作不能被执行的的有限沉积,可能会导致 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("第 {} 组工作,第 {} 次执行实现", groupId, j);
            }
        });
    }
    executorService.shutdown();}

// 测试后果
23:20:15.066 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 1 组工作,第 1 次执行实现
23:20:16.069 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 1 组工作,第 2 次执行实现
23:20:17.070 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 1 组工作,第 3 次执行实现
23:20:18.070 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 1 组工作,第 4 次执行实现
23:20:19.071 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 2 组工作,第 1 次执行实现
23:23:280.071 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 2 组工作,第 2 次执行实现
23:23:281.072 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 2 组工作,第 3 次执行实现
23:23:282.072 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 2 组工作,第 4 次执行实现
23:23:283.073 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 3 组工作,第 1 次执行实现
23:23:284.074 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 3 组工作,第 2 次执行实现
23:23:285.074 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 3 组工作,第 3 次执行实现
23:23:286.075 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 3 组工作,第 4 次执行实现
23:23:287.075 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 4 组工作,第 1 次执行实现
23:23:288.075 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 4 组工作,第 2 次执行实现
23:23:289.076 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 4 组工作,第 3 次执行实现
23:20:30.076 [pool-2-thread-1] INFO  o.i.i.t.Test_newSingleThreadExecutor - 第 4 组工作,第 4 次执行实现

图解

  • 代码new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
  • 介绍:只创立一个执行线程工作的线程池,如果出现意外终止则再创立一个。
  • 危险:同样这也是一个无界队列寄存待执行线程,有限沉积下会呈现 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("第 {} 组工作,第 {} 次执行实现", groupId, j);
            }
        });
    }
    executorService.shutdown();
    
    // 测试后果
    23:25:59.818 [pool-2-thread-2] INFO  o.i.i.test.Test_newCachedThreadPool - 第 2 组工作,第 1 次执行实现
    23:25:59.818 [pool-2-thread-3] INFO  o.i.i.test.Test_newCachedThreadPool - 第 3 组工作,第 1 次执行实现
    23:25:59.818 [pool-2-thread-1] INFO  o.i.i.test.Test_newCachedThreadPool - 第 1 组工作,第 1 次执行实现
    23:25:59.818 [pool-2-thread-4] INFO  o.i.i.test.Test_newCachedThreadPool - 第 4 组工作,第 1 次执行实现
    23:25:00.823 [pool-2-thread-4] INFO  o.i.i.test.Test_newCachedThreadPool - 第 4 组工作,第 2 次执行实现
    23:25:00.823 [pool-2-thread-1] INFO  o.i.i.test.Test_newCachedThreadPool - 第 1 组工作,第 2 次执行实现
    23:25:00.823 [pool-2-thread-2] INFO  o.i.i.test.Test_newCachedThreadPool - 第 2 组工作,第 2 次执行实现
    23:25:00.823 [pool-2-thread-3] INFO  o.i.i.test.Test_newCachedThreadPool - 第 3 组工作,第 2 次执行实现
    23:25:01.823 [pool-2-thread-4] INFO  o.i.i.test.Test_newCachedThreadPool - 第 4 组工作,第 3 次执行实现
    23:25:01.823 [pool-2-thread-1] INFO  o.i.i.test.Test_newCachedThreadPool - 第 1 组工作,第 3 次执行实现
    23:25:01.824 [pool-2-thread-2] INFO  o.i.i.test.Test_newCachedThreadPool - 第 2 组工作,第 3 次执行实现
    23:25:01.824 [pool-2-thread-3] INFO  o.i.i.test.Test_newCachedThreadPool - 第 3 组工作,第 3 次执行实现
    23:25:02.824 [pool-2-thread-1] INFO  o.i.i.test.Test_newCachedThreadPool - 第 1 组工作,第 4 次执行实现
    23:25:02.824 [pool-2-thread-4] INFO  o.i.i.test.Test_newCachedThreadPool - 第 4 组工作,第 4 次执行实现
    23:25:02.825 [pool-2-thread-3] INFO  o.i.i.test.Test_newCachedThreadPool - 第 3 组工作,第 4 次执行实现
    23:25:02.825 [pool-2-thread-2] INFO  o.i.i.test.Test_newCachedThreadPool - 第 2 组工作,第 4 次执行实现
}

图解

  • 代码new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>())
  • 介绍:首先 SynchronousQueue 是一个生产生产模式的阻塞工作队列,只有有工作就须要有线程执行,线程池中的线程能够重复使用。
  • 危险:如果线程工作比拟耗时,又大量创立,会导致 OOM

4. newScheduledThreadPool

public static void main(String[] args) {ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    executorService.schedule(() -> {logger.info("3 秒后开始执行");
    }, 3, TimeUnit.SECONDS);
    executorService.scheduleAtFixedRate(() -> {logger.info("3 秒后开始执行,当前每 2 秒执行一次");
    }, 3, 2, TimeUnit.SECONDS);
    executorService.scheduleWithFixedDelay(() -> {logger.info("3 秒后开始执行,后续提早 2 秒");
    }, 3, 2, TimeUnit.SECONDS);
}

// 测试后果
23:28:32.442 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3 秒后开始执行
23:28:32.444 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3 秒后开始执行,当前每 2 秒执行一次
23:28:32.444 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3 秒后开始执行,后续提早 2 秒
23:28:34.441 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3 秒后开始执行,当前每 2 秒执行一次
23:28:34.445 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3 秒后开始执行,后续提早 2 秒
23:28:36.440 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3 秒后开始执行,当前每 2 秒执行一次
23:28:36.445 [pool-2-thread-1] INFO  o.i.i.t.Test_newScheduledThreadPool - 3 秒后开始执行,后续提早 2 秒

图解

  • 代码public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue()); }
  • 介绍:这就是一个比拟有意思的线程池了,它能够提早定时执行,有点像咱们的定时工作。同样它也是一个有限大小的线程池 Integer.MAX_VALUE。它提供的调用办法比拟多,包含:scheduleAtFixedRatescheduleWithFixedDelay,能够按需抉择提早执行形式。
  • 危险:同样因为这是一组有限容量的线程池,所以仍旧又 OOM 危险。

四、线程池应用场景阐明

什么时候应用线程池?

说简略是当为了给老板省钱的时候,因为应用线程池能够升高服务器资源的投入,让每台机器尽可能更大限度的应用 CPU。

???? 那你这么说必定没方法升职加薪了!

所以如果说的高大上一点,那么是在合乎科特尔法令和阿姆达尔定律 的状况下,引入线程池的应用最为正当。啥意思呢,还得简略说!

如果:咱们有一套电商服务,用户浏览商品的并发拜访速率是:1000 客户 / 每分钟,均匀每个客户在服务器上的耗时 0.5 分钟。依据利特尔法令,在任何时刻,服务端都承当着 1000*0.5=500 个客户的业务处理量。过段时间大促了,并发拜访的用户扩了一倍 2000 客户了,那怎么保障服务性能呢?

  1. 进步服务器并发解决的业务量,即进步到 2000×0.5=1000
  2. 缩小服务器均匀解决客户申请的工夫,即缩小到:2000×0.25=500

所以:在有些场景下会把串行的申请接口,压缩成并行执行,如图 22-5

然而,线程池的应用会随着业务场景变动而不同,如果你的业务须要大量的应用线程池,并十分依赖线程池,那么就不可能用 Executors 工具类中提供的办法。因为这些线程池的创立都不够精细化,也非常容易造成 OOM 危险,而且随着业务场景逻辑不同,会有 IO 密集型和 CPU 密集型。

最终,大家应用的线程池都是应用 new ThreadPoolExecutor() 创立的,当然也有基于 Spring 的线程池配置 org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor

可你想过吗,同样一个接口在有流动时候怎么办、有大促时候怎么办,可能你过后设置的线程池是正当的,然而一到流量十分大的时候就很不适宜了,所以如果能动静调整线程池就十分有必要了。而且应用 new ThreadPoolExecutor() 形式创立的线程池是能够通过提供的 set 办法进行动静调整的。有了这个动静调整的办法后,就能够把线程池包装起来,在配合动静调整的页面,动静更新线程池参数,就能够十分不便的调整线程池了。

五、获取线程池监控信息

你收过报警短信吗?

收过,中午还有报警机器人打电话呢!崴,你的零碎有个机器睡着了,快起来看看!!!

所以,如果你高频、高依赖线程池,那么有一个残缺的监控零碎,就非重要了。总不能线上挂了,你还不晓得!

可监控内容

办法 含意
getActiveCount() 线程池中正在执行工作的线程数量
getCompletedTaskCount() 线程池已实现的工作数量,该值小于等于 taskCount
getCorePoolSize() 线程池的外围线程数量
getLargestPoolSize() 线程池已经创立过的最大线程数量。通过这个数据能够晓得线程池是否满过,也就是达到了 maximumPoolSize
getMaximumPoolSize() 线程池的最大线程数量
getPoolSize() 线程池以后的线程数量
getTaskCount() 线程池曾经执行的和未执行的工作总数

1. 重写线程池形式监控

如果咱们想监控一个线程池的办法执行动作,最简略的形式就是继承这个类,重写办法,在办法中增加动作收集信息。

伪代码

public class ThreadPoolMonitor extends ThreadPoolExecutor {

    @Override
    public void shutdown() {
        // 统计已执行工作、正在执行工作、未执行工作数量
        super.shutdown();}

    @Override
    public List<Runnable> shutdownNow() {
        // 统计已执行工作、正在执行工作、未执行工作数量
        return super.shutdownNow();}

    @Override
    protected void beforeExecute(Thread t, Runnable r) {// 记录开始工夫}

    @Override
    protected void afterExecute(Runnable r, Throwable t) {// 记录实现耗时}
    
    ...
}

2. 基于 IVMTI 形式监控

这块是监控的重点,因为咱们不太可能让每一个须要监控的线程池都来重写的形式记录,这样的革新老本太高了。

那么除了这个笨办法外,能够抉择应用基于 JVMTI 的形式,进行开发监控组件。

JVMTI:JVMTI(JVM Tool Interface)位于 jpda 最底层,是 Java 虚拟机所提供的 native 编程接口。JVMTI 能够提供性能剖析、debug、内存治理、线程剖析等性能。

基于 jvmti 提供的接口服务,使用 C ++ 代码 (win32-add_library) 在 Agent_OnLoad 里开发监控服务,并生成 dll 文件。开发实现后在 java 代码中退出 agentpath,这样就能够监控到咱们须要的信息内容。

环境筹备

  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 整个文件夹复制到和工程案例同层级目录下,便于 include 援用)

配置信息:(门路相干批改为本人的)

  1. C++ 开发工具 Clion 配置
    1. 配置地位;Settings->Build,Execution,Deployment->Toolchains

    1. MinGM 配置:D:\Program Files (x86)\Dev-Cpp\MinGW64
  2. java 调试时配置

    1. 配置地位:Run/Debug Configurations ->VM options
    2. 配置内容:-agentpath:E:\itstack\git\github.com\itstack-jvmti\cmake-build-debug\libitstack_jvmti.dll

2.1 先做一个监控例子

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("测试后果:" + obj);
        } catch (Exception e) {// 屏蔽异样}
    }
}

class PartnerEggResourceImpl {Logger logger = Logger.getLogger("PartnerEggResourceImpl");
    public Object queryUserInfoById(String userId) {logger.info("依据用户 Id 获取用户信息" + userId);
        if (null == userId) {throw new NullPointerException("依据用户 Id 获取用户信息,空指针异样");
        }
        return userId;
    }
}

c++ 监控

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

using namespace std;

// 异样回调函数
static void JNICALL
callbackException(jvmtiEnv *jvmti_env, JNIEnv *env, jthread thr, jmethodID methodId, jlocation location,
jobject exception, jmethodID catch_method, jlocation catch_location) {
// 取得办法对应的类
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);

// 获取指标办法的起止地址和完结地址
jlocation start_location_ptr;    // 办法的起始地位
jlocation end_location_ptr;      // 用于办法的完结地位
jvmti_env->GetMethodLocation(methodId, &start_location_ptr, &end_location_ptr);

// 输入测试后果
cout << "测试后果 - 定位类的签名:" << class_signature << endl;
cout << "测试后果 - 定位办法信息:" << method_name_ptr << "->" << method_signature_ptr << endl;
cout << "测试后果 - 定位办法地位:" << start_location_ptr << "->" << end_location_ptr + 1 << endl;
cout << "测试后果 - 异样类的名称:" << exception_class_name << endl;

cout << "测试后果 - 输入异样信息(能够剖析行号):" << 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);
    // 创立一个新的环境
    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;
    // 设置以后环境
    gb_jvmti->AddCapabilities(&caps);
    // 创立一个新的回调函数
    jvmtiEventCallbacks callbacks;
    memset(&callbacks, 0, sizeof(callbacks));
    // 异样回调
    callbacks.Exception = &callbackException;
    // 设置回调函数
    gb_jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
    // 开启事件监听(JVMTI_EVENT_EXCEPTION)
    gb_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, nullptr);
    return JNI_OK;
}

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) {}

测试后果

在 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
信息: 依据用户 Id 获取用户信息 null
java.lang.NullPointerException: 依据用户 Id 获取用户信息,空指针异样
    at org.itstack.demo.PartnerEggResourceImpl.queryUserInfoById(TestLocationException.java:26)
    at org.itstack.demo.TestLocationException.main(TestLocationException.java:13)
测试后果 - 定位类的签名:Lorg/itstack/demo/PartnerEggResourceImpl;
测试后果 - 定位办法信息:queryUserInfoById -> (Ljava/lang/String;)Ljava/lang/Object;
测试后果 - 定位办法地位:0 -> 43
测试后果 - 异样类的名称:Ljava/lang/NullPointerException;
测试后果 - 输入异样信息(能够剖析行号):
  • 这就是基于 JVMTI 的形式进行监控,这样的形式能够做到非入侵代码。不须要硬编码,也就节俭了人力,否则所有人都会进行开发监控内容,而这部分内容与业务逻辑并无关系。

2.2 扩大线程监控

其实办法差不多,都是基于 C ++ 开发 DLL 文件,引入应用。不过这部分代码会监控办法信息,并采集线程的执行内容。

static void JNICALL callbackMethodEntry(jvmtiEnv *jvmti_env, JNIEnv *env, jthread thr, jmethodID method) {
    // 取得办法对应的类
    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("[线程监控]: 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);
    // 创立一个新的环境
    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;
    // 设置以后环境
    gb_jvmti->AddCapabilities(&gb_capa);
    // 创立一个新的回调函数
    jvmtiEventCallbacks callbacks;
    memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
    // 办法回调
    callbacks.MethodEntry = &callbackMethodEntry;
    // 设置回调函数
    gb_jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));

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

    // 注册事件监听(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;
}
  • 从监控的代码能够看到,这里有线程的 start、stop、join、interrupt 等,并能够记录执行信息。
  • 另外这里监控的办法执行回调,SetEventCallbacks(&callbacks, sizeof(callbacks)); 以及相应事件的增加。

六、总结

  • 如果说你所经验的业务体量很小,那么简直并不需要如此简单的技术栈深度学习,甚至简直不须要扩大各类性能,也不须要监控。但究竟有一些须要造飞机的大厂,他们的业务体量宏大,并发数高,让本来可能就是一个简略的查问接口,也要做熔断、降级、限流、缓存、线程、异步、预热等等操作。
  • 知其然才敢用,如果对一个技术点不是太相熟,就不要胡乱应用,否则遇到的 OOM 并不是那么好复现,尤其是在并发场景下。当然如果你们技术体系中有各种服务,比方流量复现、链路追踪等等,那么还好。
  • 又扯到了这,一个保持学习、分享、积淀的男人!好了,如果有错字、内容不精确,欢送间接怼给我,我喜爱承受。但不要欺侮我哦哈哈哈哈哈!

七、系列举荐

  • Thread.start(),它是怎么让线程启动的呢?-%E5%AE%83%E6%98%AF%E6%80%8E%E4%B9%88%E8%AE%A9%E7%BA%BF%E7%A8%8B%E5%90%AF%E5%8A%A8%E7%9A%84%E5%91%A2.html)
  • Thread 线程,状态转换、办法应用、原理剖析
  • 手写线程池,对照学习 ThreadPoolExecutor 线程池实现原理!
  • ReentrantLock 之 AQS 原理剖析和实际应用
  • 如果你只写 CRUD,那这种技术你永远碰不到
退出移动版