<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JVM探針與字節(jié)碼技術(shù)

          共 9950字,需瀏覽 20分鐘

           ·

          2021-10-09 17:33

          JVM探針是自jdk1.5以來,由虛擬機提供的一套監(jiān)控類加載器和符合虛擬機規(guī)范的代理接口,結(jié)合字節(jié)碼指令能夠讓開發(fā)者實現(xiàn)無侵入的監(jiān)控功能。如:監(jiān)控生產(chǎn)環(huán)境中的函數(shù)調(diào)用情況或動態(tài)增加日志輸出等等。雖然在常規(guī)的業(yè)務(wù)中不會有太多用武之地,但是作為一項高級的技術(shù)手段也應(yīng)該是資深開發(fā)人員的必備技能之一。同時,它也是企業(yè)級開發(fā)和生產(chǎn)環(huán)境部署不可或缺的技術(shù)方案,是對當下流行的APM的一種補充,因為使用探針技術(shù)能夠?qū)崿F(xiàn)比常規(guī)APM平臺更細粒度的監(jiān)控。

          哪些方面適合使用探針技術(shù):

            (1) 如果你發(fā)現(xiàn)生產(chǎn)環(huán)境上有些問題無法在測試或開發(fā)環(huán)境中復(fù)現(xiàn)

            (2)?如果你希望在不修改源碼的情況下為你的應(yīng)用添加一些輸出日志

            (3)?如果在剛發(fā)布的生產(chǎn)包中發(fā)現(xiàn)了一個bug,而你又不希望被它阻斷,希望有一個臨時的補救措施

          一、JVM探針:Instrumentation

          ?使用探針只需要一條附加選項:-javaagent:[=<選項>],作為探針(代理)的jar包必須滿足兩個條件:1. MANIFEST.MF文件需要增加Premain-Class項,說明啟動類。2. 啟動類必須聲明一個靜態(tài)函數(shù),它的入?yún)⑹? String和Instrumentation。因此,一個常見的啟動類可能像這樣:

          package?aa.bb.cc;

          public?class?PremainAgent?{
          ????public?static?void?premain(String?agentArgs,?Instrumentation?inst)?{
          ????????//?TODO
          ????}
          }

          MANIFEST.MF

          premain-class:?aa.bb.cc.PremainAgent

          如果使用maven作為構(gòu)建工具,需要在pom文件中添加構(gòu)建插件

          ??
          ????org.apache.maven.plugins??
          ????maven-jar-plugin??
          ????3.2.0??
          ??????
          ??????????
          ??????????????
          ????????????????aa.bb.cc.PremainAgent??
          ????????????
          ??
          ????????
          ??
          ????
          ??

          如果你還引入了其它依賴希望同時打包,那么你應(yīng)該使用assembly插件替代

          ??
          ??org.apache.maven.plugins??
          ??maven-assembly-plugin??
          ??2.4??
          ????
          ??????
          ??????jar-with-dependencies??
          ????
          ??
          ??????
          ????????
          ????????.PremainAgent
          ????????true??
          ????????true??
          ??????
          ??
          ????
          ??
          ??
          ??
          ????
          ??????
          ??????package??
          ????????
          ????????single??
          ??????
          ??
          ????
          ??
          ??

          兩個重要的類

          Instrumentation:?由JDK提供的一個探針類,它會負責加載用戶自定義的ClassFileTransformer

          ClassFileTransformer:?字節(jié)碼轉(zhuǎn)換類,jvm在加載class文件前會先調(diào)用它,對所有類加載器有效

          具體用法稍后會做詳細介紹。

          總結(jié):JVM探針只是提供了一種讓開發(fā)人員能夠在類加載加載class文件前主動介入的一種方法,具體如何操作需要開發(fā)人員了解Java虛擬機規(guī)范以及字節(jié)碼的相關(guān)知識。

          二、棧幀與指令集

          棧幀(Stack Frame)是用于支持虛擬機進行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)。它是虛擬機運行時數(shù)據(jù)區(qū)中的虛擬機棧的棧元素。棧幀存儲了方法的局部變量表、操作數(shù)棧、動態(tài)連接和方法返回地址等信息。每一個方法從調(diào)用開始至執(zhí)行完成的過程,都對應(yīng)著一個棧幀在虛擬機里面從入棧到出棧的過程。

          在編譯程序代碼的時候,棧幀中需要多大的局部變量表,多深的操作數(shù)棧都已經(jīng)完全確定了。因此一個棧幀需要分配多少內(nèi)存,不會受到程序運行期變量數(shù)據(jù)的影響,而僅僅取決于具體的虛擬機實現(xiàn)。

          局部變量表(Local Variable Table)是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。并且在Java編譯為Class文件時,就已經(jīng)確定了該方法所需要分配的局部變量表的最大容量。局部變量表類似一個數(shù)組結(jié)構(gòu),虛擬機在訪問局部變量表的時候會使用下標作為引用,普通方法的局部變量表中第0位索引默認是用于傳遞方法所屬對象實例的引用this。

          操作數(shù)棧(Operand Stack)和局部變量表一樣,在編譯時期就已經(jīng)確定了該方法所需要分配的局部變量表的最大容量。當一個方法剛剛開始執(zhí)行的時候,這個方法的操作數(shù)棧是空的,在方法執(zhí)行的過程中,會有各種字節(jié)碼指令往操作數(shù)棧中寫入和提取內(nèi)容,也就是出棧/入棧操作。例如,在做算術(shù)運算的時候是通過操作數(shù)棧來進行的,又或者在調(diào)用其它方法的時候是通過操作數(shù)棧來進行參數(shù)傳遞的。

          動態(tài)鏈接(Dynamic Linking)每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支付方法調(diào)用過程中的動態(tài)連接。在類加載階段中的解析階段會將符號引用轉(zhuǎn)為直接引用,這種轉(zhuǎn)化也稱為靜態(tài)解析。另外的一部分將在每一次運行時期轉(zhuǎn)化為直接引用,這部分稱為動態(tài)連接。

          返回地址:當一個方法開始執(zhí)行后,只有2種方式可以退出這個方法,方法返回指令和異常退出。無論采用任何退出方式,在方法退出之后,都需要返回到方法被調(diào)用的位置,程序才能繼續(xù)執(zhí)行,方法返回時可能需要在棧幀中保存一些信息。一般來說,方法正常退出時,調(diào)用者的PC計數(shù)器的值可以作為返回地址,棧幀中會保存這個計數(shù)器值。而方法異常退出時,返回地址是要通過異常處理器表來確定的,棧幀中一般不會保存這部分信息。

          JVM指令集并非是對Java語句的直接翻譯,由于指令只使用1個字節(jié)表示,所以指令集最多只能包含256種指令。因此,一條Java語句一般會對應(yīng)多條底層指令。每一條指令都有與之對應(yīng)的助記符,我們可以通過官方資料查看它們對應(yīng)關(guān)系:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html。為了幫助大家更加直觀的理解字節(jié)碼指令,我將通過三個用例分別解釋。

          從一個簡單的加法函數(shù)開始,我們可以使用javac將.java文件編譯成.class,再通過javap -c查看它的字節(jié)碼文件

          public?int?add(int?x,?int?y)?{??
          ????return?x?+?y;??
          }
          public?add(II)I
          ????ILOAD?1?//?將局部變量表中#1變量入棧
          ????ILOAD?2?//?將局部變量表中#2變量入棧
          ????IADD?//?調(diào)用整型數(shù)相加(兩個數(shù)出棧,再將結(jié)果入棧)
          ????IRETURN?//?返回棧頂?shù)慕Y(jié)果
          ????MAXSTACK?=?2?//?最大棧數(shù)2
          ????MAXLOCALS?=?3?//?最大本地變量數(shù)3

          第一行是它的函數(shù)簽名,2~7行的注釋分別是對指令的解釋。ILOAD,IADD,IRETURN分別是整型數(shù)的入棧,加法和返回操作。大家可以將add方法修改為靜態(tài)函數(shù)后重新編譯,看看MAXLOCALS是否有變化。

          接下來我們把函數(shù)變得復(fù)雜一些,嘗試對函數(shù)的執(zhí)行時間做一個計算并輸出

          public?int?add(int?x,?int?y)?{??
          ????long?t?=?System.nanoTime();??
          ????int?ret?=?x?+?y;??
          ????t?=?System.nanoTime()?-?t;??
          ????System.out.println(t);??
          ????return?ret;??
          }
          public?add(II)I
          ????INVOKESTATIC?java/lang/System.nanoTime?()J?//?調(diào)用靜態(tài)函數(shù),結(jié)果long入棧
          ????LSTORE?3?//?將棧頂?shù)膌ong保存到局部變量#3
          ????ILOAD?1
          ????ILOAD?2
          ????IADD
          ????ISTORE?5?//?將棧頂?shù)膇nt保存到局部變量#5
          ????INVOKESTATIC?java/lang/System.nanoTime?()J
          ????LLOAD?3?//?局部變量#3入棧
          ????LSUB?//?從棧頂彈出兩個long相減
          ????LSTORE?3?//?結(jié)果保存到變量#3
          ????GETSTATIC?java/lang/System.out?:?Ljava/io/PrintStream;?//?獲取靜態(tài)引用
          ????LLOAD?3?//?局部變量#3入棧
          ????INVOKEVIRTUAL?java/io/PrintStream.println?(J)V?//?調(diào)用函數(shù)
          ????ILOAD?5?//??局部變量#5入棧
          ????IRETURN
          ????MAXSTACK?=?4
          ????MAXLOCALS?=?6

          第2行結(jié)尾的J表示函數(shù)返回值是long類型。第14行結(jié)尾的V表示println函數(shù)的返回值是void。第12行到第14行的指令對應(yīng)代碼的System.out.println(t),特別需要注意的是INVOKEVIRTUAL指令實際上需要從操作數(shù)棧獲取兩個數(shù),第一個數(shù)是在執(zhí)行了GETSTATIC后入棧的對象引用。

          我們再次修改函數(shù),這一次我們引入比較和循環(huán)語句,盡管代碼的邏輯不太正常,但這并不妨礙我們理解

          public?int?add(int?x,?int?y)?{??
          ????if(x?>?1)?{??
          ????????return?x?+?y;??
          ????}??
          ????for(int?i?=?0;?i?????????x?++;??
          ????}??
          ????return?x?-?y;??
          }
          public?add(II)I
          ????ILOAD?1
          ????ICONST_1?//?將一個常整型數(shù)1入棧
          ????IF_ICMPLE?L0?//?比較如果操兩個操作數(shù)是小于等于的關(guān)系則成立,否則跳轉(zhuǎn)到L0的位置繼續(xù)
          ????ILOAD?1
          ????ILOAD?2
          ????IADD
          ????IRETURN
          ???L0
          ????ICONST_0?//?將常整型數(shù)0入棧
          ????ISTORE?3?//?棧頂數(shù)保存到局部變量#3
          ???L1
          ????ILOAD?3
          ????ILOAD?2
          ????IF_ICMPGE?L2?//?比較棧頂?shù)膬蓚€操作數(shù)是否是大于等于的關(guān)系,如果不成立則跳轉(zhuǎn)到L2
          ????IINC?1?1?//?局部變量#1?自增1
          ????IINC?3?1?//?局部變量#3?自增1
          ????GOTO?L1?//?跳轉(zhuǎn)到L1執(zhí)行
          ???L2
          ????ILOAD?1
          ????ILOAD?2
          ????ISUB
          ????IRETURN
          ????MAXSTACK?=?2
          ????MAXLOCALS?=?4

          當我們使用字節(jié)碼直接操作虛擬機中的底層代碼的時候,基本上就是通過改變局部變量表和操作數(shù)棧來改變程序的邏輯。還記得根據(jù)Java虛擬機規(guī)范,MAXSTACK和MAXLOCALS是在.java文件被編譯成.class就被確定下來的嗎,如果我們要對方法做出修改勢必會引入新的局部變量,這時就難免需要對MAXSTACK和MAXLOCALS做重新計算。好在目前流行的字節(jié)碼框架已經(jīng)可以自動幫助我們完成這項任務(wù)。

          三、ASM框架?

          ASM是一個比較硬核的字節(jié)碼框架,也是轉(zhuǎn)換效率最高的工具。下面是常用類的介紹:

          1. ClassReader

          按照Java虛擬機規(guī)范(JVMS)中定義的方式來解析class文件中的內(nèi)容,在遇到合適的字段時調(diào)用ClassVisitor中相對應(yīng)的方法。
          ClassReader(final byte[] classFile)
          構(gòu)造方法,通過class字節(jié)碼數(shù)據(jù)加載
          ClassReader(final String className) throws IOException
          通過class全路徑名從ClassLoader加載

          2. ClassVisitor

          java中的訪問者,提供一系列方法由ClassReader調(diào)用。調(diào)用的順序如下:visit -> visitSource -> visitModule -> visitNestHost -> visitOuterClass -> visitAnnotation -> visitTypeAnnotation -> visitAttribute -> visitNestMember -> visitPermittedSubclass -> visitInnerClass -> visitRecordComponent -> visitField -> visitMethod -> visitEnd

          3. ClassWriter

          ClassVisitor的子類,通過它生成最后的字節(jié)碼。并且它可以幫助重新計算MAXSTACK和MAXLOCALS

          4. ModuleVisitor

          Java中模塊的訪問者,作為ClassVisitor.visitModule方法的返回值

          5. AnnotationVisitor

          Java中注解的訪問者,作為ClassVisito中visitTypeAnnotationvisitTypeAnnotation的返回值

          6. FieldVisitor

          Java中字段的訪問者,作為ClassVisito.visitField的返回值

          7. MethodVisitor

          Java中方法的訪問者,作為ClassVisito.visitMethod的返回值

          • visitMethodInsn?方法調(diào)用指令

          • visitVarInsn?局部變量調(diào)用指令

          • visitInsn(int)?訪問一個零參數(shù)要求的字節(jié)碼指令,如LSUB

          • visitLdcInsn?把一個常量放到棧頂

          • visitInvokeDynamicInsn?動態(tài)方法調(diào)用

          • visitFieldInsn?調(diào)用/訪問某個字段

          8. AnalyzerAdapter

          MethodVisitor的子類,使用它重新計算最大操作數(shù)棧(MAXSTACK)

          9. LocalVariablesSorter

          MethodVisitor的子類,使用它重新計算局部變量表(MAXLOCALS)的索引

          • newLocal?創(chuàng)建局部變量

          通過IDEA的Plugins安裝ASM Bytecode Viewer Support Kotlin,我們可以借助這個插件來幫助我們生成大部分代碼,具體用法這里就贅述了。

          總結(jié):有了以上知識基礎(chǔ),我們可以完成一個簡單的demo來感受探針和字節(jié)碼技術(shù)的強大。

          一個計算函數(shù)執(zhí)行時間的完整用例

          1. 在IDEA中創(chuàng)建一個典型的maven工程

          2.編寫pom文件

          "1.0"?encoding="UTF-8"?>??
          "http://maven.apache.org/POM/4.0.0"??
          ?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"??
          ?xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/xsd/maven-4.0.0.xsd">??
          ????com.learnhow.study
          ????1.0
          ??????jar
          ????agent??
          ??
          ??????
          ??????????
          ????????????org.ow2.asm??
          ????????????asm??
          ????????????9.2
          ????????
          ??
          ??????????
          ????????????org.ow2.asm??
          ????????????asm-commons
          ????????????9.2
          ????????
          ??
          ????
          ??
          ??
          ??????
          ??????????
          ??????????????
          ????????????????org.apache.maven.plugins??
          ????????????????maven-compiler-plugin??
          ????????????????3.8.1??
          ??????????????????
          ????????????????????<source>[your?jdk?version]source>??
          ????????????????????[your?jdk?version]??
          ????????????????
          ??
          ????????????
          ??
          ??????????????
          ????????????????org.apache.maven.plugins??
          ????????????????maven-assembly-plugin??
          ????????????????2.4??
          ??????????????????
          ??????????????????????
          ????????????????????????jar-with-dependencies??
          ????????????????????
          ??
          ??????????????????????
          ??????????????????????????
          ????????????????????????????[your?package].PremainAgent
          ????????????????????????????true??
          ????????????????????????????true??
          ????????????????????????
          ??
          ????????????????????
          ??
          ????????????????
          ??
          ??????????????????
          ??????????????????????
          ????????????????????????package??
          ??????????????????????????
          ????????????????????????????single??
          ????????????????????????
          ??
          ????????????????????
          ??
          ????????????????
          ??
          ????????????
          ??
          ????????
          ??
          ????
          ??

          帶[]的部分請換成你的本地環(huán)境。

          3.PremainAgent類

          public?class?PremainAgent?{??
          ????public?static?void?premain(String?agentArgs,?Instrumentation?inst)?{??
          ????????inst.addTransformer(new?XClassFileTransformer());??
          ????}??
          }

          4.XClassFileTransformer類

          public?class?XClassFileTransformer?implements?ClassFileTransformer?{
          ????@Override
          ????public?byte[]?transform(ClassLoader?loader,
          ????????????????????????????String?className,
          ????????????????????????????Class?classBeingRedefined,
          ????????????????????????????ProtectionDomain?protectionDomain,
          ????????????????????????????byte[]?classfileBuffer)?throws?IllegalClassFormatException?{
          ????????try?{
          ????????????ClassReader?cr?=?new?ClassReader(classfileBuffer);
          ????????????ClassWriter?cw?=?new?ClassWriter(ClassWriter.COMPUTE_MAXS);
          ????????????cr.accept(new?NanoTimerClassVisitor(cw),?ClassReader.SKIP_DEBUG);
          ????????????byte[]?cc?=?cw.toByteArray();
          ????????????return?cc;
          ????????}?catch?(IOException?e)?{

          ????????}
          ????????return?null;
          ????}
          }

          transform方法返回null或者new byte[0]表示對當前字節(jié)碼文件不進行修改。ClassWriter.COMPUTE_MAXS表示框架會自動計算MAXSTACK和MAXLOCALS,ClassReader.SKIP_DEBUG表示當字節(jié)碼中包含調(diào)試信息的時候,會忽略不會觸發(fā)回調(diào)。

          5.NanoTimerClassVisitor類

          public?class?NanoTimerClassVisitor?extends?ClassVisitor?{
          ????private?String?className;

          ????public?NanoTimerClassVisitor(ClassVisitor?classVisitor)?{
          ????????super(ASM9,?classVisitor);
          ????}

          ????@Override
          ????public?void?visit(int?version,?int?access,?String?name,?String?signature,?String?superName,?String[]?interfaces)?{
          ????????this.className?=?name;
          ????????super.visit(version,?access,?name,?signature,?superName,?interfaces);
          ????}

          ????@Override
          ????public?MethodVisitor?visitMethod(int?access,?String?name,?String?descriptor,?String?signature,?String[]?exceptions)?{
          ????????MethodVisitor?mv?=?super.visitMethod(access,?name,?descriptor,?signature,?exceptions);
          ????????if?(Objects.nonNull(mv)?&&?!name.equals("")?&&?!name.equals(""))?{
          ????????????NanoTimerMethodVisitor?methodVisitor?=?new?NanoTimerMethodVisitor(mv,?className,?access,?name,?descriptor);
          ????????????return?methodVisitor.refactor();
          ????????}
          ????????return?mv;
          ????}

          ????class?NanoTimerMethodVisitor?extends?MethodVisitor?{
          ????????private?AnalyzerAdapter?analyzerAdapter;
          ????????private?LocalVariablesSorter?localVariablesSorter;
          ????????private?int?timeOpcode;
          ????????private?int?outOpcode;
          ????????private?String?className;
          ????????private?int?methodAccess;
          ????????private?String?methodName;
          ????????private?String?methodDescriptor;

          ????????public?NanoTimerMethodVisitor(MethodVisitor?methodVisitor,?String?className,?int?methodAccess,
          ?????????????????????????????????String?methodName,?String?methodDescriptor)?{
          ????????????super(ASM9,?methodVisitor);
          ????????????this.className?=?className;
          ????????????this.methodAccess?=?methodAccess;
          ????????????this.methodName?=?methodName;
          ????????????this.methodDescriptor?=?methodDescriptor;
          ????????????//?使用AnalyzerAdapter計算最大操作數(shù)棧
          ????????????analyzerAdapter?=?new?AnalyzerAdapter(className,?methodAccess,?methodName,?methodDescriptor,?this);
          ????????????//?LocalVariablesSorter重新計算局部變量的索引并自動更新字節(jié)碼中的索引引用
          ????????????localVariablesSorter?=?new?LocalVariablesSorter(methodAccess,?methodDescriptor,?analyzerAdapter);
          ????????}

          ????????public?MethodVisitor?refactor()?{
          ????????????return?localVariablesSorter;
          ????????}

          ????????@Override
          ????????public?void?visitCode()?{
          ????????????super.visitCode();
          ????????????mv.visitMethodInsn(INVOKESTATIC,?"java/lang/System",?"nanoTime",?"()J",?false);
          ????????????timeOpcode?=?localVariablesSorter.newLocal(Type.LONG_TYPE);
          ????????????mv.visitVarInsn(LSTORE,?timeOpcode);
          ????????}

          ????????@Override
          ????????public?void?visitInsn(int?opcode)?{
          ????????????if?((opcode?>=?IRETURN?&&?opcode?<=?RETURN)?||?opcode?==?ATHROW)?{
          ????????????????mv.visitMethodInsn(INVOKESTATIC,?"java/lang/System",?"nanoTime",?"()J",?false);
          ????????????????mv.visitVarInsn(LLOAD,?timeOpcode);
          ????????????????mv.visitInsn(LSUB);
          ????????????????mv.visitVarInsn(LSTORE,?timeOpcode);

          ????????????????mv.visitLdcInsn(className?+?"."?+?methodName?+?"(ns):");
          ????????????????outOpcode?=?localVariablesSorter.newLocal(Type.getType(String.class));
          ????????????????mv.visitVarInsn(ASTORE,?outOpcode);

          ????????????????mv.visitVarInsn(ALOAD,?outOpcode);
          ????????????????mv.visitVarInsn(LLOAD,?timeOpcode);
          ????????????????mv.visitInvokeDynamicInsn("makeConcatWithConstants",?"(Ljava/lang/String;J)Ljava/lang/String;",?new?Handle(Opcodes.H_INVOKESTATIC,?"java/lang/invoke/StringConcatFactory",?"makeConcatWithConstants",?"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;",?false),?new?Object[]{"\u0001\u0001"});
          ????????????????mv.visitVarInsn(ASTORE,?outOpcode);

          ????????????????mv.visitFieldInsn(GETSTATIC,?"java/lang/System",?"out",?"Ljava/io/PrintStream;");
          ????????????????mv.visitVarInsn(ALOAD,?outOpcode);
          ????????????????mv.visitMethodInsn(INVOKEVIRTUAL,?"java/io/PrintStream",?"println",?"(Ljava/lang/String;)V",?false);
          ????????????}
          ????????????super.visitInsn(opcode);
          ????????}
          ????}
          }

          6. 通過assembly插件對項目進行打包生成:agent-1.0-jar-with-dependencies.jar

          7. 運行一個目標項目,并添加虛擬機指令-javaagent,就可以看到執(zhí)行效果

          如何查看生成后的代碼

          計算函數(shù)執(zhí)行時間是一個非常簡單的功能,我們很容易一次性寫正確。但是如果需要代理的邏輯比較復(fù)雜,而探針程序又不像普通程序一樣方便做斷點調(diào)試。我們?nèi)绾尾拍軌蚝芊奖阒郎傻拇a是否正確呢?這里告訴大家一個訣竅。回到我們XClassFileTransformer類,增加兩行代碼:

          public?class?XClassFileTransformer?implements?ClassFileTransformer?{
          ????@Override
          ????public?byte[]?transform(ClassLoader?loader,
          ????????????????????????????String?className,
          ????????????????????????????Class?classBeingRedefined,
          ????????????????????????????ProtectionDomain?protectionDomain,
          ????????????????????????????byte[]?classfileBuffer)?throws?IllegalClassFormatException?{
          ????????try?{
          ????????????ClassReader?cr?=?new?ClassReader(classfileBuffer);
          ????????????ClassWriter?cw?=?new?ClassWriter(ClassWriter.COMPUTE_MAXS);
          ????????????cr.accept(new?NanoTimerClassVisitor(cw),?ClassReader.SKIP_DEBUG);
          ????????????byte[]?cc?=?cw.toByteArray();
          ????????????FileOutputStream?fos?=?new?FileOutputStream("./cc.class");
          ????????????fos.write(cc);
          ????????????return?cc;
          ????????}?catch?(IOException?e)?{

          ????????}
          ????????return?null;
          ????}
          }

          第13、14行代碼的功能是將生成的字節(jié)碼輸出到本地文件中,然后我們通過IDEA打開這個.class文件,看看新增加的代碼是否如我們預(yù)期的那樣。

          總結(jié):JVM代理發(fā)生在類加載器加載.class文件前,因此我們能夠動態(tài)修改字節(jié)碼。通過ASM這類字節(jié)碼框架,使得開發(fā)人員即使對字節(jié)碼指令不是很熟悉依然能夠操作。當然,Java的探針技術(shù)除了和被代理的項目同時啟動以外還提供了一種熱部署的方案,受篇幅限制不再贅述,如果大家有興趣可以給我留言。


          ? 作者?|??冷豪

          來源 |??cnblogs.com/learnhow/p/15364955.html

          加鋒哥微信:?java1239??
          圍觀鋒哥朋友圈,每天推送Java干貨!

          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黑人大屌视频 | 大战老熟女丝袜高跟 | 国产公妇乱婬XXXX看一本毛片 | 亚洲欧美视频一区 | 日韩无码免费看 |