揭密Java常用性能调优工具的底层实现原理( 三 )


JMX通过各种 MBean(Managed Bean) 来传递消息 。外界可以获取被管理的资源的状态和操纵MBean的行为 。常见的MBean如下表所示 。
 
ClassLoadingMXBean获取类装载信息,已装载、已卸载量
CompilationMXBean获取编译器信息
GarbageCollectionMXBean获取GC信息,但他仅仅提供了GC的次数和GC花费总时间
MemoryManagerMXBean提供了内存管理和内存池的名字信息
MemoryMXBean提供整个虚拟机中内存的使用情况
MemoryPoolMXBean提供获取各个内存池的使用信息
OperatingSystemMXBean提供操作系统的简单信息
RuntimeMXBean提供运行时当前JVM的详细信息
ThreadMXBean提供对线程使用的状态信息
名称解释
 
下面举一个小例子,让大家有直观的认识,如下:
class JMXUtil {private static final long MB = 1024*1024L;public static void main(String[] args) {printMemoryInfo();}static void printMemoryInfo() {MemoryMXBean memory = ManagementFactory.getMemoryMXBean();MemoryUsage headMemory = memory.getHeapMemoryUsage();String info = String.format("ninit: %st max: %st used: %st committed: %st use rate: %sn",headMemory.getInit() / MB + "MB",headMemory.getMax() / MB + "MB", headMemory.getUsed() / MB + "MB",headMemory.getCommitted() / MB + "MB",headMemory.getUsed() * 100 / headMemory.getCommitted() + "%");System.out.print(info);MemoryUsage nonheadMemory = memory.getNonHeapMemoryUsage();info = String.format("init: %st max: %st used: %st committed: %st use rate: %sn",nonheadMemory.getInit() / MB + "MB",nonheadMemory.getMax() / MB + "MB", nonheadMemory.getUsed() / MB + "MB",nonheadMemory.getCommitted() / MB + "MB",nonheadMemory.getUsed() * 100 / nonheadMemory.getCommitted() + "%");System.out.println(info);}}运行后的输出如下:
init: 124MB max: 1751MB used: 2MB committed: 119MB use rate: 2%init: 2MBmax: 0MBused: 5MBcommitted: 7MBuse rate: 66%一般监控系统用的比较多,也就是和JavaAgent方式结合以后,就能在指定了监控的目标Java进程后,打印目标Java进程的一些系统信息,如堆和非堆的参数 。Arthas中dashboard命令中显示的堆外内存大小就是通过JavaAgent加上JMX来实现的 。
【揭密Java常用性能调优工具的底层实现原理】相关的工具有:
(1)JDK自带的jconsole或VisualVM
(2)监控系统Spring Boot Actuator
https://www.baeldung.com/spring-boot-actuators
(3)vjtools中的vjmxcli
https://github.com/DarLiner/vjtools
7、JVMTIJVMTI 本质上是对JVM内部的许多事件进行了埋点 。通过这些埋点可以给外部提供当前上下文的一些信息 。甚至可以接受外部的命令来改变下一步的动作 。外部程序一般利用C/C++实现一个JVMTIAgent,在JVMTIAgent里面注册一些JVM事件的回调 。当事件发生时JVMTI调用这些回调方法 。JVMTIAgent可以在回调方法里面实现自己的逻辑 。通过JVMTI,可以实现对JVM的多种操作,它通过接口注册各种事件勾子,在JVM事件触发时,同时触发预定义的勾子,以实现对各个JVM事件的响应,事件包括类文件加载、异常产生与捕获、线程启动和结束、进入和退出临界区、成员变量修改、GC开始和结束、方法调用进入和退出、临界区竞争与等待、VM启动与退出等等 。
另外还有一种是JavaAgent,其底层的实现就是利用了JVMTI,不过可以使用Java语言来实现,但是功能没有JVMTIAgent强大 。
现在假设有一个需求,监控应用抛出的异常,如果出现异常,就在监控系统中提醒,这时候就需要JVMTI来实现了 。
使用C++编写JVMTIAgent,分别实现Agent_OnLoad()和Agent_OnUnload()函数,另外注册异常回调函数,在发生异常时,打印异常的详细信息,实现如下:
#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);//异常类名称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);} // Agent_OnLoad函数,如果agent是在启动的时候加载的,也就是在vm参数里通过-agentlib来指定,那在启动过程中就会去执行这个agent里的Agent_OnLoad函数JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm,char *options,void *reserved) {cout << "Agent_OnLoad(" << vm << ")" << endl;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;}// Agent_OnUnload函数,在agent做卸载的时候调用,不过貌似基本上很少实现它JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) { }


推荐阅读