为了账号安全,请及时绑定邮箱和手机立即绑定

为什么不用nativeagent

标签:
Java

如果常看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来做的。只不过这种场景对比监控来说,确实少之又少。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
1.6万
获赞与收藏
380

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消