如果常看jvmti的文档,我们会发现jvm提供了两种agent。一种是javaagent,一种是native agent。
世面上的大多数apm程序为什么都是用javaagent呢。
对比两个agent
java agent可以说上手非常方便,都是java的代码,和平时编写的java代码可以说没区别。
public static void premain(final String args, Instrumentation inst) {
}
public static void agentmain(final String args, Instrumentation inst) {
}
这是agent的入口函数,premain是在-javaagent启动的时候,main方法执行之前执行。agentmain则是通过动态load触发的入口。下面看一段简单的案例。
public class Main {
public static void premain(final String args, Instrumentation inst) {
agentmain(args, inst);
}
public static void agentmain(final String args, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
className = className.replace("/", ".");
if (!className.contains("$") && (isSpecialClass(className) || isReTransformClass(className, args))) {
ClassReader cr;
cr = new ClassReader(classfileBuffer);
ClassNode cn = new ClassNode();
cr.accept(cn, 0);
if (SYSTEM.contains(className)) {
SystemTransform at = new SystemTransform();
at.trans(cn);
} else {
CustomTransform at = new CustomTransform();
at.trans(cn);
}
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
byte[] toByte = cw.toByteArray();
return toByte;
}
return null;
}
}, true);
Class[] allLoadedClasses = inst.getAllLoadedClasses();
List<Class> retransformClasses = new ArrayList<Class>(allLoadedClasses.length);
for (Class clazz : allLoadedClasses) {
String name = clazz.getName();
if (isSpecialClass(name) || isReTransformClass(name, args)) {
if (inst.isModifiableClass(clazz) && !name.contains("$")) {
retransformClasses.add(clazz);
}
}
}
try {
if(retransformClasses.size()>0){
inst.retransformClasses(retransformClasses.toArray(new Class[0]));
}
} catch (UnmodifiableClassException e) {
e.printStackTrace();
}
}
public static boolean isSpecialClass(String className) {
return SYSTEM.contains(className);
}
public static boolean isReTransformClass(String className, String args) {
if (NOCLASS.equals(args)) {
return false;
}
if (className.contains(args)) {
return true;
}
return false;
}
}
中间有一段通过asm做字节码注入的逻辑。了解更多请查看
javaagent可以无缝的往一个java进程上执行一段java代码,并且可以获取到字节码进行类的转化。
native agent的入口如下
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {}
JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *vm) {}
看到了c++代码,瞬间研发难度增加。
上面是加载和卸载时候调用的方法。
native agent大部分操作都是基于jvmti的事件,基于事件写回调,这个是字节码注入做不到的。例如像查看所有的异常,javaagent就需要把所有的类的方法都改造一下,都做try catch。如果类特别多的话,耗费资源的大户。jvmti则是简单注入一下事件,加一个回调函数就可以。
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
jvmtiEnv *jvmti = NULL;
jint ret = vm->GetEnv(reinterpret_cast<void **>(&jvmti), JVMTI_VERSION_1_2);
if (ret != JNI_OK) {
fprintf(stderr, "ERROR: Couldn't get JVMTI environment");
return JNI_ERR;
}
// capbilities
jvmtiCapabilities caps;
std::memset(&caps, 0, sizeof(caps));
caps.can_generate_monitor_events = 1;
caps.can_generate_exception_events = 1;
auto err = jvmti->AddCapabilities(&caps);
if (err != JVMTI_ERROR_NONE) {
fprintf(stderr, "ERROR: Unable to AddCapabilities JVMTI");
return JNI_ERR;
}
// callback
jvmtiEventCallbacks cb;
cb.Exception = &jvmti_EventException;
jvmti->SetEventCallbacks(&cb, sizeof(cb));
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, NULL);
return JNI_OK;
}
开启注册开关,然后设置好回调函数。
void JNICALL jvmti_EventException
(jvmtiEnv *jvmti_env,
JNIEnv
*jni_env,
jthread thread,
jmethodID
method,
jlocation location,
jobject
exception,
jmethodID catch_method,
jlocation
catch_location) {}
回调函数是固定的参数,基本足够我们使用了,只要进行收集就可以做到。
native agent也可以做字节码注入的这些事情,但是还得编写java代码来做。
怎么看native agent更加强大。为什么还是用javaagent的多。
业务分析
1.编写难度不一样
javaagent编写容易,上手简单,只要是java程序员问题不大。native agent则需要c/c++编写,语法难度大,而且容易造成crash。
2. 业务需求大多数在java代码上
我们常见的监控,需要jvm的信息,也同时需要业务的信息。例如我们想做调用链追踪。这个已经是业务代码层的问题,而不是jvm层面的问题,需要用字节码改造或者替换jar包的方式进行指标的暴露。和业务相关的部分,nativeagent则是无能为力。
native的部分优势
我们常见的问题都是业务问题,那是不是没有native agent的使用呢,其实还是有的,例如性能调优,我们想做一些cpu和线程的信息采集,或者是异常的汇总,锁的时间分析。这些都是native agent才能做到,可以说更贴近jvm的事情都是需要native agent来做的。只不过这种场景对比监控来说,确实少之又少。
共同学习,写下你的评论
评论加载中...
作者其他优质文章