范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

javaagent介绍使用实现详解

  javaagent介绍
  jdk提供了一种强大的可以对已有class代码进行运行时注入修改的能力。 javaagent可以在启动时通过-javaagent:agentJarPath或运行时attach加载agent包的方式使用,通过javaagent我们可以对特定的类进行字节码修改, 在方法执行前后注入特定的逻辑。 通过字节码修改,可以实现监控tracing、性能分析、在线诊断、代码热更新热部署等等各种能力。 监控tracing: 分布式tracing框架的Java类库(比如skywalking, brave, opentracing-java)常使用javaagent实现,因为tracing需要在各个第三方框架内注入tracing数据的统计收集逻辑,比如要在grpc、kafka中发送消息前后收集tracing日志,但是这些第三方的jar包我们不方便修改它们的代码,使用javaagent就成为了很好的选择。 性能分析: 很多性能分析软件例如jprofiler使用javaagent技术,一般分析分为sampling和instrumentation两种方式,sample是通过类似jstack的方式采集方法的执行栈,instrumentatino就是修改字节码来收集方法的执行次数、耗时等信息。 在线诊断: arthas这样的软件使用javaagent技术在运行时将诊断逻辑注入到已有代码中,实现watch,trace等功能 代码热更新、热部署: 通过javaagent技术,还能够实现Java代码的热更新,减少Java服务重启次数,提升开发效率,比如开源的https://github.com/HotswapProjects/HotswapAgent和https://github.com/dcevm/dcevm 使用编写、打包、使用javaagent
  我们以[javaagent-example](https://github.com/liuzhengyang/javaagent-example)项目为例使用字节码实现一个最简单的AOP功能,在某个方法执行前打印字符串。
  编写javaagent需要在jar包中创建META-INF/MANIFEST.MF来配置agent的入口类等信息,通过maven的maven-assembly-plugin插件把resources文件夹下META-INF/MANIFEST.MF文件打包到jar包中。(
  maven pom相关配置示例如下。(除了maven-assembly-plugin,还可以用maven-shade-plugin)                            org.apache.maven.plugins             maven-compiler-plugin                              1.8                 1.8                                            org.apache.maven.plugins             maven-source-plugin             3.0.1                                                   attach-sources                     verify                                              jar-no-fork                                                                                  org.apache.maven.plugins             maven-assembly-plugin             2.6                                                   jar-with-dependencies                                                       src/main/resources/META-INF/MANIFEST.MF                                                                                 assemble-all                     package                                              single                                                                                            ${basedir}/src/main/resources                               ${basedir}/src/main/java               
  同时我们还需要在pom.xml添加我们要使用的字节码修改框架asm      org.ow2.asm     asm-all     5.1 
  然后我们添加MANIFEST.MF文件(在resources/META-INF文件夹下,如果没有则进行创建)
  Premain-Class和Agent-Class都配置成agent的入口类。Can-Redefine-Classes表示agent是否需要redefine的能力,默认为false,还有一个Can-Retransform-Classes配置, 我们这里虽然声明了true但是其实没有使用redfine能力。 Manifest-Version: 1.0 Premain-Class: com.lzy.javaagent.AgentMain Agent-Class: com.lzy.javaagent.AgentMain Can-Redefine-Classes: true
  最后编写Agent入口类,也就是上面的com.lzy.javaagent.AgentMain
  javaagent的核心功能集中在通过premain/agentmain获得的Instrumentation对象上,通过Instrumentation 对象可以添加ClassFileTransformer、调用redefine/retransform方法,以实现修改类代码的能力。 我们要实现的简单的AOP,就是在类加载前,给Instrumentation添加我们的自定义的ClassFileTransformer, ClassFileTransformer读取加载的类,然后通过字节码工具进行解析、修改,在AOP目标类的方法的执行前后打印我们想打印的字符串。 具体实现如下,其中ClassFileTransformer使用javassist框架进行字节码修改,后续的文章我们会详细介绍javassist的使用。
  AgentMain接收Instrumentation和String参数,这里我们把String参数用来指定AOP目标类 public class AgentMain { 	public static void premain(String agentOps, Instrumentation inst) { 		instrument(agentOps, inst); 	}  	public static void agentmain(String agentOps, Instrumentation inst) { 		instrument(agentOps, inst); 	}  	/** 	 * agentOps is aop target classname 	 */ 	private static void instrument(String agentOps, Instrumentation inst) { 		System.out.println(agentOps); 		inst.addTransformer(new AOPTransformer(agentOps)); 	} }
  AOPTransformer实现ClassFileTransformer,在加载指定的类时,对类进行修改在方法调用前增加代码,打印方法名。 /**  * @author liuzhengyang  * 2022/4/13  */ public class AOPTransformer implements ClassFileTransformer {      private final String className;      public AOPTransformer(String className) {         this.className = className;     }      /**      * 注意这里的className是 a/b/C这样的而不是a.b.C      */     @Override     public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {         if (className == null) {             // 返回null表示不修改类字节码,和返回classfileBuffer是一样的效果。             return null;         }         if (className.equals(this.className.replace(".", "/"))) {             ClassPool classPool = ClassPool.getDefault();             classPool.appendClassPath(new LoaderClassPath(loader));             classPool.appendSystemPath();             try {                 CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));                 CtMethod[] declaredMethods = ctClass.getDeclaredMethods();                 for (CtMethod declaredMethod : declaredMethods) {                     declaredMethod.insertBefore("System.out.println("before invoke"+ declaredMethod.getName() + "");");                 }                 return ctClass.toBytecode();             } catch (Exception e) {                 e.printStackTrace();             }         }         return classfileBuffer;     } }
  然后通过mvn clean package进行打包,在target目录下可以得到一个fatjar(包含javassist等依赖),名为javaagent-1.0-SNAPSHOT-jar-with-dependencies.jar
  然后我们就可以通过-javaagent:/tmp/javaagent-1.0-SNAPSHOT-jar-with-dependencies.jar=com.lzy.javaagent.Test 来使用agent了,注意-javaagent:后面要换成自己的agentjar包的绝对路径,=后面是传入的参数,我们这里的com.lzy.javaagent.Test是我们要aop的类。 如果是IDEA中使用,可以
  例如我们编写一个简单的Test类 package com.lzy.javaagent;  /**  * @author liuzhengyang  * 2022/4/13  */ public class Test {     public void hello() {         System.out.println("hello");     }      public static void main(String[] args) {         new Test().hello();     } }
  在idea中添加先运行一次,然后修改Run Configuration,在vm options中添加-javaagent:/Users/liuzhengyang/Code/opensource/javaagent-example/target/javaagent-1.0-SNAPSHOT-jar-with-dependencies.jar=com.lzy.javaagent.Test 运行,就可以看到AOP的效果了 com.lzy.javaagent.Test before invokemain before invokehello hello 通过bytebuddy获取Instrumentation
  有时修改-javaagent参数不是特别方便,比如使用方可能不方便或不知道怎么修改启动参数,有没有通过maven依赖代码调用的方式使用javaagent呢? 通过bytebuddy可以实现这一功能。
  首先pom依赖中添加byte-buddy-agent的maven依赖      net.bytebuddy     byte-buddy-agent     1.11.22 
  然后通过ByteBuddyAgent.install(),就可以很方便的获得Instrumentation对象,接下来就可以添加ClassFileTransformer、调用redefine等等。
  关于bytebuddy的使用和实现原理,我们会在后面文章中详细介绍。 public class TestByteBuddyInstall {     public static void main(String[] args) {         Instrumentation install = ByteBuddyAgent.install();         System.out.println(install); //        install.addTransformer();     } } Instrumentation接口介绍
  我们对java.lang.instrument.Instrumentation类的重要方法进行一下介绍
  方法
  说明
  void addTransformer(ClassFileTransformer transformer)
  添加一个Transformer
  void addTransformer(ClassFileTransformer transformer, boolean canRetransform)
  添加一个Transformer,如果canRetransform为true这个transformer在类被retransform的时候会调用
  void appendToBootstrapClassLoaderSearch(JarFile jarfile)
  添加一个jar包让bootstrap classloader能够搜索到
  void appendToSystemClassLoaderSearch(JarFile jarfile)
  添加一个jar包让system classloader能够搜索到
  Class[] getAllLoadedClasses()
  获取当前所有已经加载的类
  Class[] getInitiatedClasses(ClassLoader loader)
  获取某个classloader已经初始化过的类
  long getObjectSize(Object objectToSize)
  获取某个对象的大小(不包含引用的传递大小,比如一个String字段,只计算这个字段的引用4byte)
  void redefineClasses(ClassDefinition... definitions)
  对某个类进行redefine修改代码,注意默认jdk只能修改方法体,不能进行增减字段方法等,dcevm jdk可以实现更强大的修改功能
  boolean removeTransformer(ClassFileTransformer transformer)
  从Instrumentation中删除Transformer
  void retransformClasses(Class<?>... classes)
  让一个已经加载的类重新transform,不过在retransform过程中和redefine一样,不能对类结构进行变更,只能修改方法体 javaagent使用注意事项javaagent的premain和agentmain的类是通过System ClassLoader(AppClassLoader)加载的,所以如果要和业务代码通信,需要考虑classloader不同的情况,一般要通过反射(可以传入指定classloader加载类)和业务代码通信。 注意依赖冲突的问题,比如agent的fatjar中包含了某个第三方的类,业务代码中也包含了相同的第三方但是不同版本的类,由于classloader存在父类优先委派加载的情况,可能会导致类加载异常,所以一般会通过shaded修改第三方类库的包名或者通过classloader隔离 实现META-INF/MANIFEST.MF文件
  javaagent在打包时,按照规范需要在jar包中的META-INF/MANIFEST.MF文件中声明javaagent的配置信息, 其中最关键的是Agent-Class、Premain-Class,这两个表示使用动态attach和-javaagent启动时调用的类, JVM会在这个类中寻找对应的agentmain和premain方法执行。 Can-Redefine-Classes、Can-Retransform-Classes表示此javaagent是否需要使用Instrumentation的 redefine和retransform的能力。 修改类的字节码有两个时机,一个javaagent通过Instrumentation.addTransformer方法注入ClassFileTransformer, 在类加载时,jvm会调用各个ClassFileTransformer,ClassFileTransformer可以修改类的字节码,但是如果要在类已经加载后再去修改它的字节码, 就需要使用redefine和retransform。 Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Created-By: Apache Maven 3.6.3 Built-By: liuzhengyang Build-Jdk: 11.0.11 Agent-Class: org.hotswap.agent.HotswapAgent Can-Redefine-Classes: true Can-Retransform-Classes: true Implementation-Title: java-reload-agent-assembly Implementation-Version: 1.0-SNAPSHOT Premain-Class: org.hotswap.agent.HotswapAgent Specification-Title: java-reload-agent-assembly Specification-Version: 1.0-SNAPSHOT -javaagent: 执行流程参数解析
  例如当我们通过-javaagent:/Users/liuzhengyang/Code/opensource/java-reload-agent/java-reload-agent-assembly/target/java-reload-agent.jar 启动时,
  以下代码位于jdk的arguments.cpp中,jvm解析传入的启动参数,对于-javaagent参数,会解析agent jar包路径和其他参数,并放到AgentLibraryList中。 AgentLibraryList是AgentLibrary的链表,AgentLibrary包含agent的名称参数等信息。 else if (match_option(option, "-javaagent:", &tail)) { #if !INCLUDE_JVMTI       jio_fprintf(defaultStream::error_stream(),         "Instrumentation agents are not supported in this VM ");       return JNI_ERR; #else       if (tail != NULL) {         size_t length = strlen(tail) + 1;         char *options = NEW_C_HEAP_ARRAY(char, length, mtArguments);         jio_snprintf(options, length, "%s", tail);         add_instrument_agent("instrument", options, false);         // java agents need module java.instrument         if (!create_numbered_property("jdk.module.addmods", "java.instrument", addmods_count++)) {           return JNI_ENOMEM;         }       } #endif /  void Arguments::add_instrument_agent(const char* name, char* options, bool absolute_path) {   _agentList.add(new AgentLibrary(name, options, absolute_path, NULL, true)); }    // -agentlib and -agentpath arguments   static AgentLibraryList _agentList; agentLibrary加载使用
  解析完启动参数后,jvm会创建vm,agentLibrary也是在这个过程中加载的。
  create_vm方法判断Arguments::init_agents_at_startup()为true(AgentLibraryList不为空列表),则执行create_vm_init_agents。
  以下代码位于thread.cpp中。 jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {   extern void JDK_Version_init();    // Preinitialize version info.   VM_Version::early_initialize();    // 省略其他代码...    // Launch -agentlib/-agentpath and converted -Xrun agents   if (Arguments::init_agents_at_startup()) {     create_vm_init_agents();   }    // 省略其他代码... }
  create_vm_init_agents方法负责初始化各个AgentLibrary,lookup_agent_on_load负责查找加载AgentLibrary对应的JVMTI动态链接库,然后调用对应JVMTI动态链接库的on_load_entry回调方法 void Threads::create_vm_init_agents() {   extern struct JavaVM_ main_vm;   AgentLibrary* agent;    JvmtiExport::enter_onload_phase();    for (agent = Arguments::agents(); agent != NULL; agent = agent->next()) {     // CDS dumping does not support native JVMTI agent.     // CDS dumping supports Java agent if the AllowArchivingWithJavaAgent diagnostic option is specified.     if (Arguments::is_dumping_archive()) {       if(!agent->is_instrument_lib()) {         vm_exit_during_cds_dumping("CDS dumping does not support native JVMTI agent, name", agent->name());       } else if (!AllowArchivingWithJavaAgent) {         vm_exit_during_cds_dumping(           "Must enable AllowArchivingWithJavaAgent in order to run Java agent during CDS dumping");       }     }      OnLoadEntry_t  on_load_entry = lookup_agent_on_load(agent);      if (on_load_entry != NULL) {       // Invoke the Agent_OnLoad function       jint err = (*on_load_entry)(&main_vm, agent->options(), NULL);       if (err != JNI_OK) {         vm_exit_during_initialization("agent library failed to init", agent->name());       }     } else {       vm_exit_during_initialization("Could not find Agent_OnLoad function in the agent library", agent->name());     }   }    JvmtiExport::enter_primordial_phase(); }
  lookup_agent_on_load方法负责查找对应的jvmti动态链接库,对于javaagent,jvm中已经内置了对应的动态库名为instrument,位于jdk的lib文件夹下,比如mac下 是lib/libinstrument.dylib,linux中是lib/libinstrument.so。 // Find a command line agent library and return its entry point for //         -agentlib:  -agentpath:   -Xrun // num_symbol_entries must be passed-in since only the caller knows the number of symbols in the array. static OnLoadEntry_t lookup_on_load(AgentLibrary* agent,                                     const char *on_load_symbols[],                                     size_t num_symbol_entries) {   OnLoadEntry_t on_load_entry = NULL;   void *library = NULL;    if (!agent->valid()) {     char buffer[JVM_MAXPATHLEN];     char ebuf[1024] = "";     const char *name = agent->name();     const char *msg = "Could not find agent library ";      // First check to see if agent is statically linked into executable     if (os::find_builtin_agent(agent, on_load_symbols, num_symbol_entries)) {       library = agent->os_lib();     } else if (agent->is_absolute_path()) {       library = os::dll_load(name, ebuf, sizeof ebuf);       if (library == NULL) {         const char *sub_msg = " in absolute path, with error: ";         size_t len = strlen(msg) + strlen(name) + strlen(sub_msg) + strlen(ebuf) + 1;         char *buf = NEW_C_HEAP_ARRAY(char, len, mtThread);         jio_snprintf(buf, len, "%s%s%s%s", msg, name, sub_msg, ebuf);         // If we can"t find the agent, exit.         vm_exit_during_initialization(buf, NULL);         FREE_C_HEAP_ARRAY(char, buf);       }     } else {       // Try to load the agent from the standard dll directory       if (os::dll_locate_lib(buffer, sizeof(buffer), Arguments::get_dll_dir(),                              name)) {         library = os::dll_load(buffer, ebuf, sizeof ebuf);       }       if (library == NULL) { // Try the library path directory.         if (os::dll_build_name(buffer, sizeof(buffer), name)) {           library = os::dll_load(buffer, ebuf, sizeof ebuf);         }         if (library == NULL) {           const char *sub_msg = " on the library path, with error: ";           const char *sub_msg2 = " Module java.instrument may be missing from runtime image.";            size_t len = strlen(msg) + strlen(name) + strlen(sub_msg) +                        strlen(ebuf) + strlen(sub_msg2) + 1;           char *buf = NEW_C_HEAP_ARRAY(char, len, mtThread);           if (!agent->is_instrument_lib()) {             jio_snprintf(buf, len, "%s%s%s%s", msg, name, sub_msg, ebuf);           } else {             jio_snprintf(buf, len, "%s%s%s%s%s", msg, name, sub_msg, ebuf, sub_msg2);           }           // If we can"t find the agent, exit.           vm_exit_during_initialization(buf, NULL);           FREE_C_HEAP_ARRAY(char, buf);         }       }     }     agent->set_os_lib(library);     agent->set_valid();   }    // Find the OnLoad function.   on_load_entry =     CAST_TO_FN_PTR(OnLoadEntry_t, os::find_agent_function(agent,                                                           false,                                                           on_load_symbols,                                                           num_symbol_entries));   return on_load_entry; }
  instrument动态链接库的实现位于java/instrumentat/share/native/libinstrument 入口为InvocationAdapter.c,on_load_entry方法实现是DEF_Agent_OnLoad方法。 createNewJPLISAgent是创建一个JPLISAgent(Java Programming Language Instrumentation Services) 创建完成JPLISAgent后,会读取保存premainClass、jarfile、bootClassPath等信息。 JNIEXPORT jint JNICALL DEF_Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) {     JPLISInitializationError initerror  = JPLIS_INIT_ERROR_NONE;     jint                     result     = JNI_OK;     JPLISAgent *             agent      = NULL;      initerror = createNewJPLISAgent(vm, &agent);     if ( initerror == JPLIS_INIT_ERROR_NONE ) {         int             oldLen, newLen;         char *          jarfile;         char *          options;         jarAttribute*   attributes;         char *          premainClass;         char *          bootClassPath;          /*          * Parse [=options] into jarfile and options          */         if (parseArgumentTail(tail, &jarfile, &options) != 0) {             fprintf(stderr, "-javaagent: memory allocation failure. ");             return JNI_ERR;         }          /*          * Agent_OnLoad is specified to provide the agent options          * argument tail in modified UTF8. However for 1.5.0 this is          * actually in the platform encoding - see 5049313.          *          * Open zip/jar file and parse archive. If can"t be opened or          * not a zip file return error. Also if Premain-Class attribute          * isn"t present we return an error.          */         attributes = readAttributes(jarfile);         if (attributes == NULL) {             fprintf(stderr, "Error opening zip file or JAR manifest missing : %s ", jarfile);             free(jarfile);             if (options != NULL) free(options);             return JNI_ERR;         }          premainClass = getAttribute(attributes, "Premain-Class");         if (premainClass == NULL) {             fprintf(stderr, "Failed to find Premain-Class manifest attribute in %s ",                 jarfile);             free(jarfile);             if (options != NULL) free(options);             freeAttributes(attributes);             return JNI_ERR;         }          /* Save the jarfile name */         agent->mJarfile = jarfile;          /*          * The value of the Premain-Class attribute becomes the agent          * class name. The manifest is in UTF8 so need to convert to          * modified UTF8 (see JNI spec).          */         oldLen = (int)strlen(premainClass);         newLen = modifiedUtf8LengthOfUtf8(premainClass, oldLen);         if (newLen == oldLen) {             premainClass = strdup(premainClass);         } else {             char* str = (char*)malloc( newLen+1 );             if (str != NULL) {                 convertUtf8ToModifiedUtf8(premainClass, oldLen, str, newLen);             }             premainClass = str;         }         if (premainClass == NULL) {             fprintf(stderr, "-javaagent: memory allocation failed ");             free(jarfile);             if (options != NULL) free(options);             freeAttributes(attributes);             return JNI_ERR;         }          /*          * If the Boot-Class-Path attribute is specified then we process          * each relative URL and add it to the bootclasspath.          */         bootClassPath = getAttribute(attributes, "Boot-Class-Path");         if (bootClassPath != NULL) {             appendBootClassPath(agent, jarfile, bootClassPath);         }          /*          * Convert JAR attributes into agent capabilities          */         convertCapabilityAttributes(attributes, agent);          /*          * Track (record) the agent class name and options data          */         initerror = recordCommandLineData(agent, premainClass, options);          /*          * Clean-up          */         if (options != NULL) free(options);         freeAttributes(attributes);         free(premainClass);     }      switch (initerror) {     case JPLIS_INIT_ERROR_NONE:       result = JNI_OK;       break;     case JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT:       result = JNI_ERR;       fprintf(stderr, "java.lang.instrument/-javaagent: cannot create native agent. ");       break;     case JPLIS_INIT_ERROR_FAILURE:       result = JNI_ERR;       fprintf(stderr, "java.lang.instrument/-javaagent: initialization of native agent failed. ");       break;     case JPLIS_INIT_ERROR_ALLOCATION_FAILURE:       result = JNI_ERR;       fprintf(stderr, "java.lang.instrument/-javaagent: allocation failure. ");       break;     case JPLIS_INIT_ERROR_AGENT_CLASS_NOT_SPECIFIED:       result = JNI_ERR;       fprintf(stderr, "-javaagent: agent class not specified. ");       break;     default:       result = JNI_ERR;       fprintf(stderr, "java.lang.instrument/-javaagent: unknown error ");       break;     }     return result; } 调用premain方法
  在Thread::create_vm方法中,会调用post_vm_initialized,回调各个JVMTI动态链接库,其中instrument中 // Notify JVMTI agents that VM initialization is complete - nop if no agents.   JvmtiExport::post_vm_initialized();
  其中instrument的JVMTI入口在InvocationAdapter.c的eventHandlerVMInit方法,eventHandlerVMInit中会调用JPLISAgent的processJavaStart方法 来启动javaagent中的premain方法。 /*  *  JVMTI callback support  *  *  We have two "stages" of callback support.  *  At OnLoad time, we install a VMInit handler.  *  When the VMInit handler runs, we remove the VMInit handler and install a  *  ClassFileLoadHook handler.  */  void JNICALL eventHandlerVMInit( jvmtiEnv *      jvmtienv,                     JNIEnv *        jnienv,                     jthread         thread) {     JPLISEnvironment * environment  = NULL;     jboolean           success      = JNI_FALSE;      environment = getJPLISEnvironment(jvmtienv);      /* process the premain calls on the all the JPL agents */     if (environment == NULL) {         abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", getting JPLIS environment failed");     }     jthrowable outstandingException = NULL;     /*      * Add the jarfile to the system class path      */     JPLISAgent * agent = environment->mAgent;     if (appendClassPath(agent, agent->mJarfile)) {         fprintf(stderr, "Unable to add %s to system class path - "                 "the system class loader does not define the "                 "appendToClassPathForInstrumentation method or the method failed ",                 agent->mJarfile);         free((void *)agent->mJarfile);         abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", appending to system class path failed");     }     free((void *)agent->mJarfile);     agent->mJarfile = NULL;      outstandingException = preserveThrowable(jnienv);     success = processJavaStart( environment->mAgent, jnienv);     restoreThrowable(jnienv, outstandingException);      /* if we fail to start cleanly, bring down the JVM */     if ( !success ) {         abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", processJavaStart failed");     } }
  processJavaStart负责调用agent jar包中的premain方法。 createInstrumentationImpl创建Instrumentation类的实例(sun.instrument.InstrumentationImpl) startJavaAgent会调用agent中的premain方法,传入Instrumentation类实例和agent参数。 /*  * If this call fails, the JVM launch will ultimately be aborted,  * so we don"t have to be super-careful to clean up in partial failure  * cases.  */ jboolean processJavaStart(   JPLISAgent *    agent,                     JNIEnv *        jnienv) {     jboolean    result;      /*      *  OK, Java is up now. We can start everything that needs Java.      */      /*      *  First make our fallback InternalError throwable.      */     result = initializeFallbackError(jnienv);     jplis_assert_msg(result, "fallback init failed");      /*      *  Now make the InstrumentationImpl instance.      */     if ( result ) {         result = createInstrumentationImpl(jnienv, agent);         jplis_assert_msg(result, "instrumentation instance creation failed");     }       /*      *  Register a handler for ClassFileLoadHook (without enabling this event).      *  Turn off the VMInit handler.      */     if ( result ) {         result = setLivePhaseEventHandlers(agent);         jplis_assert_msg(result, "setting of live phase VM handlers failed");     }      /*      *  Load the Java agent, and call the premain.      */     if ( result ) {         result = startJavaAgent(agent, jnienv,                                 agent->mAgentClassName, agent->mOptionsString,                                 agent->mPremainCaller);         jplis_assert_msg(result, "agent load/premain call failed");     }      /*      * Finally surrender all of the tracking data that we don"t need any more.      * If something is wrong, skip it, we will be aborting the JVM anyway.      */     if ( result ) {         deallocateCommandLineData(agent);     }      return result; } Can-Redefine-Classes和Can-Retransform-Classes的作用
  jvmti 运行时attach加载agent
  在启动时通过javaagent加载agent在一些情况下不太方便,比如有时候我们想对运行中的程序进行一些类的变更, 比如进行性能分析或者程序诊断,如果要修改启动参数重启,可能会导致现场被破坏,修改参数重启也不是很方便,这时jdk提供的动态attach加载agent功能就非常方便了。 arthas和jprofiler均能这种方式。
  attach和loadAgent代码实例如下,首先通过VirtualMachine.attach attach到本机的某个java进程, 得到VirtualMachine, 然后调用VirtualMachine的loadAgent方法加载调用具体的路径的javaagent jar包。
  这个是由jdk的AttachListener实现的,除了attach后加载javaagent,jdk中的jstack,jcmd等命令也都是使用AttachListener机制和jvm通信的。 String pid = "要attach的目标进程id"; String agentPath = "javaagent jar包的绝对路径"; String agentOptions = "可选的传给agentmain方法的参数"; try {     VirtualMachine virtualMachine = VirtualMachine.attach(pid);     virtualMachine.loadAgent(agentPath, agentOptions);     virtualMachine.detach(); } catch (Exception e) {     e.printStackTrace(); } attach客户端
  jvm在tmpdir目录下(linux下是/tmp)创建.java_pid文件(是进程id)用来和客户端通信, 默认情况下不会提前创建,客户端会通过向目标java进程发送QUIT信号,java进程收到QUIT后会创建这个通信文件。 VirtualMachineImpl(AttachProvider provider, String vmid)         throws AttachNotSupportedException, IOException {     super(provider, vmid);      int pid;     try {         pid = Integer.parseInt(vmid);     } catch (NumberFormatException x) {         throw new AttachNotSupportedException("Invalid process identifier");     }      // Find the socket file. If not found then we attempt to start the     // attach mechanism in the target VM by sending it a QUIT signal.     // Then we attempt to find the socket file again.     File socket_file = new File(tmpdir, ".java_pid" + pid);     socket_path = socket_file.getPath();     if (!socket_file.exists()) {         File f = createAttachFile(pid);         sendQuitTo(pid);     // ...      int s = socket();     try {         connect(s, socket_path);     } finally {         close(s);     } }
  创建完VirtualMachine以及socket通信后,就可以向jvm发送消息了。 loadAgent调用loadAgentLibrary传入instrument表示使用这个JVMTI动态链接库,并且传入args参数。 public void loadAgent(String agent, String options)         throws AgentLoadException, AgentInitializationException, IOException {     // ...     String args = agent;     if (options != null) {         args = args + "=" + options;     }     try {         loadAgentLibrary("instrument", args);     } catch (AgentInitializationException x) {     // ... }
  loadAgentLibrary /* private void loadAgentLibrary(String agentLibrary, boolean isAbsolute, String options) throws AgentLoadException, AgentInitializationException, IOException { InputStream in = execute("load", agentLibrary, isAbsolute ? "true" : "false", options); // ... }
  execute负责通过.java_pid这个socket文件和jvm进行通信发送cmd和相关参数。 InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {         int s = socket();          // connect to target VM         try {             connect(s, socket_path);         } catch (IOException x) {             close(s);             throw x;         }          try {             writeString(s, PROTOCOL_VERSION);             writeString(s, cmd);              for (int i=0; i<3; i++) {                 if (i < args.length && args[i] != null) {                     writeString(s, (String)args[i]);                 } else {                     writeString(s, "");                 }             }         // ...     } AttachListener
  AttachListener提供jvm外部和jvm通信的通道。
  AttachListener初始化时默认不启动(降低资源消耗),Attach客户端会先判断是否有.java_pid文件,如果没有 向java进程发送QUIT信号,jvm监听这个信号,如果没有启动AttachListener则会进行AttachListener创建初始化 os.cpp中的signal_thread_entry方法 switch (sig) {       case SIGBREAK: {         if (!DisableAttachMechanism) {           AttachListenerState cur_state = AttachListener::transit_state(AL_INITIALIZING, AL_NOT_INITIALIZED);           if (cur_state == AL_INITIALIZING) {             continue;           } else if (cur_state == AL_NOT_INITIALIZED) {             if (AttachListener::is_init_trigger()) {               continue; }  void AttachListener::init() {   const char thread_name[] = "Attach Listener";   Handle string = java_lang_String::create_from_str(thread_name, THREAD);   if (has_init_error(THREAD)) {     set_state(AL_NOT_INITIALIZED);     return;   }    Handle thread_group (THREAD, Universe::system_thread_group());   Handle thread_oop = JavaCalls::construct_new_instance(SystemDictionary::Thread_klass(),                        vmSymbols::threadgroup_string_void_signature(),                        thread_group,                        string,                        THREAD);   if (has_init_error(THREAD)) {     set_state(AL_NOT_INITIALIZED);     return;   }    Klass* group = SystemDictionary::ThreadGroup_klass();   JavaValue result(T_VOID);   JavaCalls::call_special(&result,                         thread_group,                         group,                         vmSymbols::add_method_name(),                         vmSymbols::thread_void_signature(),                         thread_oop,                         THREAD);   if (has_init_error(THREAD)) {     set_state(AL_NOT_INITIALIZED);     return;   }    { MutexLocker mu(Threads_lock);     JavaThread* listener_thread = new JavaThread(&attach_listener_thread_entry);      // Check that thread and osthread were created     if (listener_thread == NULL || listener_thread->osthread() == NULL) {       vm_exit_during_initialization("java.lang.OutOfMemoryError",                                     os::native_thread_creation_failed_msg());     }      java_lang_Thread::set_thread(thread_oop(), listener_thread);     java_lang_Thread::set_daemon(thread_oop());      listener_thread->set_threadObj(thread_oop());     Threads::add(listener_thread);     Thread::start(listener_thread);   } }
  其中不同类型的交互抽象成了AttachOperation,目前已经支持的 operation如下。 // names must be of length <= AttachOperation::name_length_max static AttachOperationFunctionInfo funcs[] = {   { "agentProperties",  get_agent_properties },   { "datadump",         data_dump },   { "dumpheap",         dump_heap },   { "load",             load_agent },   { "properties",       get_system_properties },   { "threaddump",       thread_dump },   { "inspectheap",      heap_inspection },   { "setflag",          set_flag },   { "printflag",        print_flag },   { "jcmd",             jcmd },   { NULL,               NULL } };
  调用VirtualMachine.load方法会发送一个load类型的AttachOperation,对应的处理函数是load_agent // Implementation of "load" command. static jint load_agent(AttachOperation* op, outputStream* out) {   // get agent name and options   const char* agent = op->arg(0);   const char* absParam = op->arg(1);   const char* options = op->arg(2);    // If loading a java agent then need to ensure that the java.instrument module is loaded   if (strcmp(agent, "instrument") == 0) {     Thread* THREAD = Thread::current();     ResourceMark rm(THREAD);     HandleMark hm(THREAD);     JavaValue result(T_OBJECT);     Handle h_module_name = java_lang_String::create_from_str("java.instrument", THREAD);     JavaCalls::call_static(&result,                            SystemDictionary::module_Modules_klass(),                            vmSymbols::loadModule_name(),                            vmSymbols::loadModule_signature(),                            h_module_name,                            THREAD);     if (HAS_PENDING_EXCEPTION) {       java_lang_Throwable::print(PENDING_EXCEPTION, out);       CLEAR_PENDING_EXCEPTION;       return JNI_ERR;     }   }    return JvmtiExport::load_agent_library(agent, absParam, options, out); } ClassFileTransformer是如何注册、调用的ClassFileTransformer注册
  Instrumentation.addTransformer会将Transformer保存到TransformerManager类中,按照能否retransform分为两个TransformerManager,每个TransformerManager中通过数组保存Transformer。 public synchronized void addTransformer(ClassFileTransformer transformer, boolean canRetransform) {     if (transformer == null) {         throw new NullPointerException("null passed as "transformer" in addTransformer");     }     if (canRetransform) {         if (!isRetransformClassesSupported()) {             throw new UnsupportedOperationException(               "adding retransformable transformers is not supported in this environment");         }         if (mRetransfomableTransformerManager == null) {             mRetransfomableTransformerManager = new TransformerManager(true);         }         mRetransfomableTransformerManager.addTransformer(transformer);         if (mRetransfomableTransformerManager.getTransformerCount() == 1) {             setHasRetransformableTransformers(mNativeAgent, true);         }     } else {         mTransformerManager.addTransformer(transformer);         if (mTransformerManager.getTransformerCount() == 1) {             setHasTransformers(mNativeAgent, true);         }     } } public synchronized void addTransformer( ClassFileTransformer    transformer) {     TransformerInfo[] oldList = mTransformerList;     TransformerInfo[] newList = new TransformerInfo[oldList.length + 1];     System.arraycopy(   oldList,                         0,                         newList,                         0,                         oldList.length);     newList[oldList.length] = new TransformerInfo(transformer);     mTransformerList = newList; } ClassFileTransformer调用
  那么ClassFileTransformer是如何被调用的呢,以类加载时调用ClassFileTransformer为例。
  在jvm加载类时,会回调各个jvmti调用类加载事件回调接口ClassFileLoadHook
  instrument jvmti的ClassFileLoadHook实现是调用InstrumentationImpl的transform方法。 void transformClassFile(             JPLISAgent *            agent,                                 JNIEnv *                jnienv,                                 jobject                 loaderObject,                                 const char*             name,                                 jclass                  classBeingRedefined,                                 jobject                 protectionDomain,                                 jint                    class_data_len,                                 const unsigned char*    class_data,                                 jint*                   new_class_data_len,                                 unsigned char**         new_class_data,                                 jboolean                is_retransformer) {     // ...省略             transformedBufferObject = (*jnienv)->CallObjectMethod(                                                 jnienv,                                                 agent->mInstrumentationImpl,                                                 agent->mTransform,                                                 moduleObject,                                                 loaderObject,                                                 classNameStringObject,                                                 classBeingRedefined,                                                 protectionDomain,                                                 classFileBufferObject,                                                 is_retransformer);             errorOutstanding = checkForAndClearThrowable(jnienv);             jplis_assert_msg(!errorOutstanding, "transform method call failed");         }          if ( !errorOutstanding ) {             *new_class_data_len = (transformedBufferSize);             *new_class_data     = resultBuffer;         }          // ...省略     }     return; }
  InstrumentationImpl的transform方法的实现是根据当前是否是retransform来选择TransformerManager,然后调用TransformerManager的transform方法。 // WARNING: the native code knows the name & signature of this method     private byte[]     transform(  Module              module,                 ClassLoader         loader,                 String              classname,                 Class<?>            classBeingRedefined,                 ProtectionDomain    protectionDomain,                 byte[]              classfileBuffer,                 boolean             isRetransformer) {         TransformerManager mgr = isRetransformer?                                         mRetransfomableTransformerManager :                                         mTransformerManager;         // module is null when not a class load or when loading a class in an         // unnamed module and this is the first type to be loaded in the package.         if (module == null) {             if (classBeingRedefined != null) {                 module = classBeingRedefined.getModule();             } else {                 module = (loader == null) ? jdk.internal.loader.BootLoader.getUnnamedModule()                                           : loader.getUnnamedModule();             }         }         if (mgr == null) {             return null; // no manager, no transform         } else {             return mgr.transform(   module,                                     loader,                                     classname,                                     classBeingRedefined,                                     protectionDomain,                                     classfileBuffer);         }     }
  TransformerManager的transform方法实现逻辑是依次调用Transformer数组中的各个Transformer(就像server中的Filter),然后把最终的bytes结果返回。 public byte[]     transform(  Module              module,                 ClassLoader         loader,                 String              classname,                 Class<?>            classBeingRedefined,                 ProtectionDomain    protectionDomain,                 byte[]              classfileBuffer) {         boolean someoneTouchedTheBytecode = false;          TransformerInfo[]  transformerList = getSnapshotTransformerList();          byte[]  bufferToUse = classfileBuffer;          // order matters, gotta run "em in the order they were added         for ( int x = 0; x < transformerList.length; x++ ) {             TransformerInfo         transformerInfo = transformerList[x];             ClassFileTransformer    transformer = transformerInfo.transformer();             byte[]                  transformedBytes = null;              try {                 transformedBytes = transformer.transform(   module,                                                             loader,                                                             classname,                                                             classBeingRedefined,                                                             protectionDomain,                                                             bufferToUse);             }             catch (Throwable t) {                 // don"t let any one transformer mess it up for the others.                 // This is where we need to put some logging. What should go here? FIXME             }              if ( transformedBytes != null ) {                 someoneTouchedTheBytecode = true;                 bufferToUse = transformedBytes;             }         }          // if someone modified it, return the modified buffer.         // otherwise return null to mean "no transforms occurred"         byte [] result;         if ( someoneTouchedTheBytecode ) {             result = bufferToUse;         }         else {             result = null;         }          return result;     } 总结
  本文我们掌握了javaagent的常见应用场景比如分布式tracing、性能分析、在线诊断、热更新等。 了解了如何创建一个javaagent来实现AOP功能以及如何使用它。 了解了javaagent在启动时加载和运行时加载的两种使用方式,还有通过ByteBuddyAgent.install()的使用方式。 了解了VirtualMachine.attach()以及loadAgent是如何通过Attach Listener与jvm通信的。 了解了jvm中的instrument动态链接库的实现。

i茅台冲上苹果应用商店免费榜第一贵州茅台方面公布,茅台电商平台i茅台App将于3月31日上线试运行,即日起可下载。在贵州茅台官宣后的第二天,北京青年报记者于3月29日查询苹果AppStore了解到,i茅台App已长江存储打入苹果供应链,打破日韩垄断,但需警惕苹果依赖症作为高端数码产品的代表,苹果手机在全球各地都有着广泛的市场,并且带动了一大批供应商的成长。目前,苹果产品的中国血统已经越来越浓厚,更值得欣喜的是,中国供应商提供的产品已经从摄像头等苹果砍单?相关企业澄清读创深圳商报记者邹晓有消息称,苹果公司正在缩减其低价版iPhoneSE3手机在中国的产量,这距离该产品推出不到三周。报道还称,苹果将2022年全年的AirPods订单减少了约100苹果考虑扩大内存芯片供应商名单,有意将中国企业纳入其中彭博3月31日报道,苹果公司正探索用于iPhone手机存储芯片的新来源,可能包括其第一家中国供应商。此前来自日本一家主要合作伙伴的生产中断,暴露了苹果公司全球供应面临的风险。知情人苹果手机容易发烫怎么办?关闭这三个功能,温度瞬间降低苹果手机容易发烫怎么办?关闭这三个功能,温度瞬间降低现在的智能手机在设计的时候就对散热有过充分的考虑,不过我们使用手机的过程还是会出现发热的情况,这也是很难避免的。手机发热的原因是iPhoneSE3价格大跳水,苹果的小屏手机还有机会吗?很多人想到iPhoneSE3可能销量不会那么高,但很多人没想到iPhoneSE3的降价会来得这么快。现在第三方平台上iPhoneSE3的价格甚至来到了2799元。相比于官网售价直降琼脂海绵,助甲醇燃料电池告别爆脾气科学家合成并应用一种新型复合材料,研制出安全耐用适应性强且具有出色柔性的自呼吸式直接甲醇燃料电池,避免了电池出现爆炸着火等安全问题。近年来,便携式可穿戴电子设备迅速发展,安全又柔软元宇宙视界VR成业绩增长新引擎,元宇宙是不是歌尔股份的未来?和立讯精密蓝思科技等公司一样,多年以来歌尔股份(002241。SZ)身上最鲜明的标签就是苹果概念。而随着元宇宙大火,该公司身上又多了一个标签。不仅如此,从近年来的业绩数据来看,与元低位数字货币元宇宙区块链智度股份1。经营范围从事网络科技计算机科技领域内的技术开发技术咨询技术服务技术转让基础软件应用软件服务(不含医用软件服务)及自助研发产品销售网络游戏开发运营与维护数码产品机电产品日用百货计人民银行部署2022年金融数字化发展业界科技应用与业务融合有待深化日前,中国人民银行金融科技委员会召开会议,总结2021年工作,研究部署2022年重点任务。会议明确,2022年要多措并举推动金融科技(FinTech)发展规划(20222025年)哈勃望远镜观测到280亿光年之外最遥远的恒星哈勃太空望远镜观测到了它所观测到的最遥远的一颗恒星,它在280亿光年外闪烁。这颗恒星的质量可能是我们太阳的50到500倍,亮度可能是太阳的数百万倍。摘自一份报告这是迄今为止对恒星最
70个Python练手项目列表,偷偷练习卷死他们,得不到的永远在骚动不管学习哪门语言都要做出实际的东西来,这个实际的东西就是项目这里整理了70个Python实战项目列表,都有完整且详细的教程,你可以从中选择自己想做的项目进行参考学习练手,你也可以从热二级管,让热能有序传输丨2的专题人类对热能的认识和使用经历了漫长的发展历程,从最初的钻木取火直接使用热能,到将热能转化为机械能和电能再进行利用,热能使用方式的变化一定程度标志着人类社会文明的发展与进步。然而,热能中本聪BTCs即将更新白皮书及代码开源中本聪链测试网中本聪链TestNet正式推出。我们邀请交易所开发者。研究人员和用户全球加入试用,获得高性能链体验,一起探索中本聪应用的无限可能测试功能包括查看链上各种数据,使用钱包Mysql8root修改密码,root无需密码或任何密码都可登录原因及解决一默认root登录Ubuntu在Mysql8。0在线安装成功后(sudoaptinstall),默认的root用户密码为空,默认监听本机,只能在本机登录,直接命令mysql即可登录对抗细菌,除菌洗衣机呵护健康洗衣机是我们生活中必不可少的电器之一,它能够帮我们清洗衣服,让我们不用再为洗衣而烦恼。下面就为各位精选了一款滚筒洗烘一体机,它们拥有大容量,满足全家人洗衣需求,而且运行噪音小,让您2022买手机千万别小气,目前口碑最好的3款手机,用三五年没问题有很多人是手机的重度使用者,每天在手机上消耗的时间很多,而且还会打游戏,那这种情况下,买手机一定不要小气,就选能用的住的,目前这三款手机口碑最好,用个三五年都没问题。第一款荣耀Ma迎接神舟13号回家,中兴Axon40成为媒体指定用机,屏下摄像头中兴手机宣布,为迎接神舟十三号回航,中兴Axon40系列成为新华网现场报道独家官方指定用机,将在4月16日6001200期间直播。同时首次公布新机正面渲染图片,以浩瀚的太空和空间站印度将生产苹果iPhone13?这是一个不寻常的信号印要生产iPhone13,很多人嗤之以鼻,笑称想要再被砸一遍?但是,这些人忘了一个问题假如三哥真能吃我们的饭,该怎么办?库克是个人精,不会干吃亏事,三天两头往印跑,肯定是对三哥很自手机卡顿严重,教你一招流畅使用明明手机刚买的时候都不卡,但是为什么用一段时间之后就变得卡到不能使用了。现在就教大家几个实用的小技巧,让你的手机不再卡顿。第一步进入手机自带的设置,每部手机的设置可能不一定完全相同硬科技投向标工业互联网年度规划出炉宁德时代拟不超60亿美元在印尼建电池项目科创板日报4月16日讯,本周,硬科技领域周报包括游戏版号恢复发放,共45款国产网络游戏获批多家科创板公司披露回购计划WHO称单剂次HPV疫苗同样有效,多家公司回应。政策工信部印发工选购净水器,记住3买3不买原则,导购都以为你是同行水是人体每天必须摄入的东西,水质的好坏也直接影响了人体的健康程度。正因为如此,越来越多的人在厨房装上了净水器。其实净水器的选择很有门道,如果不懂行很有可能被坑。我家买过几次净水器了