作者:小傅哥

博客: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秒

图解

![图 22-4 newScheduledThreadPool 执行过程

  • 代码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并不是那么好复现,尤其是在并发场景下。当然如果你们技术体系中有各种服务,比如流量复现、链路追踪等等,那么还好。
  • 又扯到了这,一个坚持学习、分享、沉淀的男人!好了,如果有错字、内容不准确,欢迎直接怼给我,我喜欢接受。但不要欺负我哦哈哈哈哈哈!

七、系列推荐

线程池的介绍和使用,以及基于jvmti设计非入侵监控的更多相关文章

  1. 开源动态可监控线程池DynamicTp介绍

    前言 使用线程池 ThreadPoolExecutor 过程中你是否有以下痛点呢? 代码中创建了一个 ThreadPoolExecutor,但是不知道那几个核心参数设置多少比较合适 凭经验设置参数值, ...

  2. 【转】线程池体系介绍及从阿里Java开发手册学习线程池的正确创建方法

    jdk1.7中java.util.concurrent.Executor线程池体系介绍 java.util.concurrent.Executor : 负责线程的使用与调度的根接口  |–Execut ...

  3. juc线程池原理(四): 线程池状态介绍

    <Thread之一:线程生命周期及五种状态> <juc线程池原理(四): 线程池状态介绍> 线程有5种状态:新建状态,就绪状态,运行状态,阻塞状态,死亡状态.线程池也有5种状态 ...

  4. Executor框架(三)线程池详细介绍与ThreadPoolExecutor

    本文将介绍线程池的设计细节,这些细节与 ThreadPoolExecutor类的参数一一对应,所以,将直接通过此类介绍线程池. ThreadPoolExecutor类 简介   java.uitl.c ...

  5. Java线程池的原理及几类线程池的介绍

    刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所 ...

  6. Java 线程池的介绍以及工作原理

    在什么情况下使用线程池? 1.单个任务处理的时间比较短 2.将需处理的任务的数量大 使用线程池的好处: 1. 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的消耗.2. 提高响应速度: ...

  7. Java 线程池详细介绍

    根据摩尔定律(Moore’s law),集成电路晶体管的数量差不多每两年就会翻一倍.但是晶体管数量指数级的增长不一定会导致 CPU 性能的指数级增长.处理器制造商花了很多年来提高时钟频率和指令并行.在 ...

  8. Spring中的线程池ThreadPoolTaskExecutor介绍

    前言: Java SE 5.0引入了ThreadPoolExecutor.ScheduledThreadPoolExecutor.Spring 2.x借助ConcurrentTaskExecutor和 ...

  9. 线程池ThreadPoolExcecutor介绍

    线程池ThreadPoolExecutor 使用Executors和ThreadPoolExecutor 并发新特性—Executor 框架与线程池

随机推荐

  1. codeforces 1426F,初学者也能做,div3的最难题

    大家好,欢迎阅读codeforces专题. 今天选择的题目是Div3比赛的最后一题,也是最难的一道题.选这道题的主要原因是帮助大家建立信心,因为有些小伙伴给我反应说之前选择的题目有些难了,觉得自己可能 ...

  2. 凭借着这份Spring面试题,我拿到了阿里,字节跳动美团的offer!

      一般问题 1.1. 不同版本的 Spring Framework 有哪些主要功能?   1.2. 什么是 Spring Framework? Spring 是一个开源应用框架,旨在降低应用程序开发 ...

  3. MindManager中主题间距/线条粗细的灵活调整

    在MindManager中,主题和线条是思维导图的基本元素,只有通过它们才能将要表达的思想呈现.并联系起来.因此,关于它们的属性设置就会多一点,如颜色.宽度.位置等.而调整主题之间的距离及线条的粗细, ...

  4. jenkins运行错误解决办法

    本地搭建好jenkis服务器,运行然后登陆上管理后台 构建好运行命令,然后进行构建出现如下错误说没有python运行环境 ,本地已经配置好python环境,并且终端能正常运行. 解决方法:jenkin ...

  5. CentOS硬软链接

    硬软链接说明 软链接: 1.软链接,以路径的形式存在.类似于Windows操作系统中的快捷方式 2.软链接可以 跨文件系统 ,硬链接不可以 3.软链接可以对一个不存在的文件名进行链接 4.软链接可以对 ...

  6. python 二维数组赋值问题

    [[]]是一个含有一个空列表元素的列表,所以[[]]*3表示3个指向这个空列表元素的引用, 修改任何一个元素都会改变整个列表 所以需要用另外一种方式进行创建多维数组,以免浅拷贝 >>> ...

  7. Django的model.py

    什么是ORM? 对象关系映射 类 >>> 表 对象 >>> 表记录 对象的属性 >>> 一条记录某个字段对应的值 django的orm不能够自动帮 ...

  8. 《技术男征服美女HR》—Fiber、Coroutine和多线程那些事

    1.起点 我叫小白,坐在这间属于华夏国超一流互联网公司企鹅巴巴的小会议室里,等着技术面试官的到来. 令我感到不舒服的,是坐在我对面的那位HR美女一个劲儿的盯着我打量!虽说本人帅气,但是也不能这么毫无顾 ...

  9. 学习abp vnext框架到精简到我的Vop框架

    学习目标 框架特点 基于.NET 5平台开发 模块化系统 极少依赖 极易扩展 ....... 框架目的 学习.NET 5平台 学习abp vnext 上图大部分功能已经实现,多数是参考(copy)ab ...

  10. Linu之用户管理【useradd】【userdel】【usermod】【passwd】【权限】

    linux下创建用户 1.用户的创建 • 简介 linux是一个多用户多任务的分时操作系统,每个用户都是在root下的一个子用户,拥有不同的权限.用户登入成功后可进入系统和自己的主目录. •实现账号的 ...