1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        IntelliJ IDEA 的 debug 調(diào)試為什么這么強?我挖出了背后的技術(shù)。。

        共 4633字,需瀏覽 10分鐘

         ·

        2022-03-02 22:26

        點擊關(guān)注公眾號,Java干貨及時送達

        來源:https://zhenbianshu.github.io

        對 Debug 的好奇

        初學(xué) Java 時,我對 IDEA 的 Debug 非常好奇,不止是它能查看斷點的上下文環(huán)境,更神奇的是我可以在斷點處使用它的 Evaluate 功能直接執(zhí)行某些命令,進行一些計算或改變當(dāng)前變量。

        剛開始語法不熟經(jīng)常寫錯代碼,重新打包部署一次代碼耗時很長,我就直接面向 Debug 開發(fā)。在要編寫的方法開始處打一個斷點,在 Evaluate 框內(nèi)一次次地執(zhí)行方法函數(shù)不停地調(diào)整代碼,沒問題后再將代碼復(fù)制出來放到 IDEA 里,再進行下一個方法的編寫,這樣就跟寫 PHP 類似的解釋性語言一樣,寫完即執(zhí)行,非常方便。

        但 Java 是靜態(tài)語言,運行之前是要先進行編譯的,難道我寫的這些代碼是被實時編譯又”注入”到我正在 Debug 的服務(wù)里了嗎?

        隨著對 Java 的愈加熟悉,我也了解了反射、字節(jié)碼等技術(shù),直到前些天的周會分享,有位同事分享了 Btrace 的使用和實現(xiàn),提到了 Java 的 ASM 框架和 JVM TI 接口。

        Btrace 修改代碼能力的實現(xiàn)與 Debug 的 Evaluate 有很多相似之處,這大大吸引了我。分享就像一個引子,從中學(xué)到的東西只是皮毛,要了解它還是要自己研究。于是自己查看資料并寫代碼學(xué)習(xí)了下其具體實現(xiàn)。

        另外,不要用破解版本了,IDEA 正版激活碼其實也可以很輕松獲取到,可以微信搜索Java技術(shù)棧,在后臺回復(fù):IDEA,可以獲取我寫的教程。

        ASM

        實現(xiàn) Evaluate 要解決的第一個問題就是怎么改變原有代碼的行為,它的實現(xiàn)在 Java 里被稱為動態(tài)字節(jié)碼技術(shù)。

        動態(tài)生成字節(jié)碼

        我們知道,我們編寫的 Java 代碼都是要被編譯成字節(jié)碼后才能放到 JVM 里執(zhí)行的,而字節(jié)碼一旦被加載到虛擬機中,就可以被解釋執(zhí)行。

        字節(jié)碼文件(.class)就是普通的二進制文件,它是通過 Java 編譯器生成的。而只要是文件就可以被改變,如果我們用特定的規(guī)則解析了原有的字節(jié)碼文件,對它進行修改或者干脆重新定義,這不就可以改變代碼行為了么。

        Java 生態(tài)里有很多可以動態(tài)生成字節(jié)碼的技術(shù),像 BCEL、Javassist、ASM、CGLib 等,它們各有自己的優(yōu)勢。有的使用復(fù)雜卻功能強大、有的簡單確也性能些差。

        ASM 框架

        ASM 是它們中最強大的一個,使用它可以動態(tài)修改類、方法,甚至可以重新定義類,連 CGLib 底層都是用 ASM 實現(xiàn)的。

        當(dāng)然,它的使用門檻也很高,使用它需要對 Java 的字節(jié)碼文件有所了解,熟悉 JVM 的編譯指令。雖然我對 JVM 的字節(jié)碼語法不熟,但有大神開發(fā)了可以在 IDEA 里查看字節(jié)碼的插件:ASM Bytecode Outline ,在要查看的類文件里右鍵選擇 Show bytecode Outline 即可以右側(cè)的工具欄查看我們要生成的字節(jié)碼。

        對照著示例,我們就可以很輕松地寫出操作字節(jié)碼的 Java 代碼了。另外,關(guān)注公眾號Java技術(shù)棧,在后臺回復(fù):面試,可以獲取我整理的 JVM 系列面試題和答案,非常齊全。

        而切到 ASMified 標簽欄,我們甚至可以直接獲取到 ASM 的使用代碼。

        常用方法

        在 ASM 的代碼實現(xiàn)里,最明顯的就是訪問者模式,ASM 將對代碼的讀取和操作都包裝成一個訪問者,在解析 JVM 加載到的字節(jié)碼時調(diào)用。

        ClassReader 是 ASM 代碼的入口,通過它解析二進制字節(jié)碼,實例化時它時,我們需要傳入一個 ClassVisitor,在這個 Visitor 里,我們可以實現(xiàn) visitMethod()/visitAnnotation() 等方法,用以定義對類結(jié)構(gòu)(如方法、字段、注解)的訪問方法。

        而 ClassWriter 接口繼承了 ClassVisitor 接口,我們在實例化類訪問器時,將 ClassWriter “注入” 到里面,以實現(xiàn)對類寫入的聲明。最新面試題整理好了,大家可以在Java面試庫小程序在線刷題。

        Instrument

        介紹

        字節(jié)碼是修改完了,可是 JVM 在執(zhí)行時會使用自己的類加載器加載字節(jié)碼文件,加載后并不會理會我們做出的修改,要想實現(xiàn)對現(xiàn)有類的修改,我們還需要搭配 Java 的另一個庫 instrument。

        instrument 是 JVM 提供的一個可以修改已加載類文件的類庫。1.6以前,instrument 只能在 JVM 剛啟動開始加載類時生效,之后,instrument 更是支持了在運行時對類定義的修改。

        使用

        要使用 instrument 的類修改功能,我們需要實現(xiàn)它的 ClassFileTransformer 接口定義一個類文件轉(zhuǎn)換器。它唯一的一個 transform() 方法會在類文件被加載時調(diào)用,在 transform 方法里,我們可以對傳入的二進制字節(jié)碼進行改寫或替換,生成新的字節(jié)碼數(shù)組后返回,JVM 會使用 transform 方法返回的字節(jié)碼數(shù)據(jù)進行類的加載。

        JVM TI

        定義完了字節(jié)碼的修改和重定義方法,但我們怎么才能讓 JVM 能夠調(diào)用我們提供的類轉(zhuǎn)換器呢?這里又要介紹到 JVM TI 了。46 張 PPT 弄懂JVM,這個分享給你。

        介紹

        JVM TI(JVM Tool Interface)JVM 工具接口是 JVM 提供的一個非常強大的對 JVM 操作的工具接口,通過這個接口,我們可以實現(xiàn)對 JVM 多種組件的操作,從 JVMTM Tool Interface 這里我們認識到 JVM TI 的強大,它包括了對虛擬機堆內(nèi)存、類、線程等各個方面的管理接口。

        JVM TI 通過事件機制,通過接口注冊各種事件勾子,在 JVM 事件觸發(fā)時同時觸發(fā)預(yù)定義的勾子,以實現(xiàn)對各個 JVM 事件的感知和反應(yīng)。

        Agent

        Agent 是 JVM TI 實現(xiàn)的一種方式。我們在編譯 C 項目里鏈接靜態(tài)庫,將靜態(tài)庫的功能注入到項目里,從而才可以在項目里引用庫里的函數(shù)。我們可以將 agent 類比為 C 里的靜態(tài)庫,我們也可以用 C 或 C++ 來實現(xiàn),將其編譯為 dll 或 so 文件,在啟動 JVM 時啟動。

        這時再來思考 Debug 的實現(xiàn),我們在啟動被 Debug 的 JVM 時,必須添加參數(shù) -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:3333,而 -agentlib 選項就指定了我們要加載的 Java Agent,jdwp 是 agent 的名字,在 linux 系統(tǒng)中,我們可以在 jre 目錄下找到 jdwp.so 庫文件。

        Java 的調(diào)試體系 jdpa 組成,從高到低分別為 jdi->jdwp->jvmti,我們通過 JDI 接口發(fā)送調(diào)試指令,而 jdwp 就相當(dāng)于一個通道,幫我們翻譯 JDI 指令到 JVM TI,最底層的 JVM TI 最終實現(xiàn)對 JVM 的操作。

        想成為架構(gòu)師,這份架構(gòu)師圖譜建議看看,少走彎路

        使用

        JVM TI 的 agent 使用很簡單,在啟動 agent 時添加 -agent 參數(shù)指定我們要加載的 agent jar包即可。

        而要實現(xiàn)代碼的修改,我們需要實現(xiàn)一個 instrument agent,它可以通過在一個類里添加 premain()agentmain() 方法來實現(xiàn)。而要實現(xiàn) 1.6 以上的動態(tài) instrument 功能,實現(xiàn) agentmain 方法即可。

        在 agentmain 方法里,我們調(diào)用 Instrumentation.retransformClasses() 方法實現(xiàn)對目標類的重定義。

        另外往一個正在運行的 JVM 里動態(tài)添加 agent,還需要用到 JVM 的 attach 功能,Sun 公司的 tools.jar 包里包含的 VirtualMachine 類提供了 attach 一個本地 JVM 的功能,它需要我們傳入一個本地 JVM 的 pid, tools.jar 可以在 jre 目錄下找到。

        agent生成

        另外,我們還需要注意 agent 的打包,它需要指定一個 Agent-Class 參數(shù)指定我們的包括 agentmain 方法的類,可以算是指定入口類吧。

        此外,還需要配置 MANIFEST.MF 文件的一些參數(shù),允許我們重新定義類。如果你的 agent 實現(xiàn)還需要引用一些其他類庫時,還需要將這些類庫都打包到此 jar 包中,下面是我的 pom 文件配置。


        ????
        ????????
        ????????????org.apache.maven.plugins
        ????????????maven-assembly-plugin
        ????????????
        ????????????????
        ????????????????????
        ????????????????????????asm.TestAgent
        ????????????????????????true
        ????????????????????????true
        ????????????????????????1.0
        ????????????????????????all-permissions
        ????????????????????

        ????????????????

        ????????????????
        ????????????????????jar-with-dependencies
        ????????????????

        ????????????

        ????????

        ????


        另外在打包時需要使mvn assembly:assembl 命令生成 jar-with-dependencies 作為 agent。

        點擊關(guān)注公眾號,Java干貨及時送達

        代碼實現(xiàn)

        我在測試時寫了一個用以上技術(shù)實現(xiàn)了一個簡單的字節(jié)碼動態(tài)修改的 Demo。

        被修改的類

        TransformTarget 是要被修改的目標類,正常執(zhí)行時,它會三秒輸出一次 “hello”。

        public?class?TransformTarget?{
        ????public?static?void?main(String[]?args)?{
        ????????while?(true)?{
        ????????????try?{
        ????????????????Thread.sleep(3000L);
        ????????????}?catch?(Exception?e)?{
        ????????????????break;
        ????????????}
        ????????????printSomething();
        ????????}
        ????}

        ????public?static?void?printSomething()?{
        ????????System.out.println("hello");
        ????}

        }

        Agent

        Agent 是執(zhí)行修改類的主體,它使用 ASM 修改 TransformTarget 類的方法,并使用 instrument 包將修改提交給 JVM。

        ?classBeingRedefined,?ProtectionDomain?protectionDomain,?byte[]?classfileBuffer)?throws?IllegalClassFormatException?{????????System.out.println("Transforming?"?+?className);????????ClassReader?reader?=?new?ClassReader(classfileBuffer);????????ClassWriter?classWriter?=?new?ClassWriter(ClassWriter.COMPUTE_FRAMES);????????ClassVisitor?classVisitor?=?new?TestClassVisitor(Opcodes.ASM5,?classWriter);????????reader.accept(classVisitor,?ClassReader.SKIP_DEBUG);????????return?classWriter.toByteArray();????}????class?TestClassVisitor?extends?ClassVisitor?implements?Opcodes?{????????TestClassVisitor(int?api,?ClassVisitor?classVisitor)?{????????????super(api,?classVisitor);????????}????????@Override????????public?MethodVisitor?visitMethod(int?access,?String?name,?String?desc,?String?signature,?String[]?exceptions)?{????????????MethodVisitor?mv?=?super.visitMethod(access,?name,?desc,?signature,?exceptions);????????????if?(name.equals("printSomething"))?{????????????????mv.visitCode();????????????????Label?l0?=?new?Label();????????????????mv.visitLabel(l0);????????????????mv.visitLineNumber(19,?l0);????????????????mv.visitFieldInsn(Opcodes.GETSTATIC,?"java/lang/System",?"out",?"Ljava/io/PrintStream;");????????????????mv.visitLdcInsn("bytecode?replaced!");????????????????mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,?"java/io/PrintStream",?"println",?"(Ljava/lang/String;)V",?false);????????????????Label?l1?=?new?Label();????????????????mv.visitLabel(l1);????????????????mv.visitLineNumber(20,?l1);????????????????mv.visitInsn(Opcodes.RETURN);????????????????mv.visitMaxs(2,?0);????????????????mv.visitEnd();????????????????TransformTarget.printSomething();????????????}????????????return?mv;????????}????}}Attacher" data-itemshowtype="0" tab="innerlink" style="color: rgb(0, 0, 0);" data-linktype="2">入口類,也是代理的 Agent-Class。

        ?classBeingRedefined,?ProtectionDomain?protectionDomain,?byte[]?classfileBuffer)?throws?IllegalClassFormatException?{????????System.out.println("Transforming?"?+?className);????????ClassReader?reader?=?new?ClassReader(classfileBuffer);????????ClassWriter?classWriter?=?new?ClassWriter(ClassWriter.COMPUTE_FRAMES);????????ClassVisitor?classVisitor?=?new?TestClassVisitor(Opcodes.ASM5,?classWriter);????????reader.accept(classVisitor,?ClassReader.SKIP_DEBUG);????????return?classWriter.toByteArray();????}????class?TestClassVisitor?extends?ClassVisitor?implements?Opcodes?{????????TestClassVisitor(int?api,?ClassVisitor?classVisitor)?{????????????super(api,?classVisitor);????????}????????@Override????????public?MethodVisitor?visitMethod(int?access,?String?name,?String?desc,?String?signature,?String[]?exceptions)?{????????????MethodVisitor?mv?=?super.visitMethod(access,?name,?desc,?signature,?exceptions);????????????if?(name.equals("printSomething"))?{????????????????mv.visitCode();????????????????Label?l0?=?new?Label();????????????????mv.visitLabel(l0);????????????????mv.visitLineNumber(19,?l0);????????????????mv.visitFieldInsn(Opcodes.GETSTATIC,?"java/lang/System",?"out",?"Ljava/io/PrintStream;");????????????????mv.visitLdcInsn("bytecode?replaced!");????????????????mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,?"java/io/PrintStream",?"println",?"(Ljava/lang/String;)V",?false);????????????????Label?l1?=?new?Label();????????????????mv.visitLabel(l1);????????????????mv.visitLineNumber(20,?l1);????????????????mv.visitInsn(Opcodes.RETURN);????????????????mv.visitMaxs(2,?0);????????????????mv.visitEnd();????????????????TransformTarget.printSomething();????????????}????????????return?mv;????????}????}}Attacher" data-itemshowtype="0" tab="innerlink" data-linktype="2">public?class?TestAgent?{
        ????public?static?void?agentmain(String?args,?Instrumentation?inst)?{
        ????????inst.addTransformer(new?TestTransformer(),?true);
        ????????try?{
        ????????????inst.retransformClasses(TransformTarget.class);
        ????????????System.out.println("Agent?Load?Done.");
        ????????}?catch?(Exception?e)?{
        ????????????System.out.println("agent?load?failed!");
        ????????}
        ????}
        }

        ?classBeingRedefined,?ProtectionDomain?protectionDomain,?byte[]?classfileBuffer)?throws?IllegalClassFormatException?{????????System.out.println("Transforming?"?+?className);????????ClassReader?reader?=?new?ClassReader(classfileBuffer);????????ClassWriter?classWriter?=?new?ClassWriter(ClassWriter.COMPUTE_FRAMES);????????ClassVisitor?classVisitor?=?new?TestClassVisitor(Opcodes.ASM5,?classWriter);????????reader.accept(classVisitor,?ClassReader.SKIP_DEBUG);????????return?classWriter.toByteArray();????}????class?TestClassVisitor?extends?ClassVisitor?implements?Opcodes?{????????TestClassVisitor(int?api,?ClassVisitor?classVisitor)?{????????????super(api,?classVisitor);????????}????????@Override????????public?MethodVisitor?visitMethod(int?access,?String?name,?String?desc,?String?signature,?String[]?exceptions)?{????????????MethodVisitor?mv?=?super.visitMethod(access,?name,?desc,?signature,?exceptions);????????????if?(name.equals("printSomething"))?{????????????????mv.visitCode();????????????????Label?l0?=?new?Label();????????????????mv.visitLabel(l0);????????????????mv.visitLineNumber(19,?l0);????????????????mv.visitFieldInsn(Opcodes.GETSTATIC,?"java/lang/System",?"out",?"Ljava/io/PrintStream;");????????????????mv.visitLdcInsn("bytecode?replaced!");????????????????mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,?"java/io/PrintStream",?"println",?"(Ljava/lang/String;)V",?false);????????????????Label?l1?=?new?Label();????????????????mv.visitLabel(l1);????????????????mv.visitLineNumber(20,?l1);????????????????mv.visitInsn(Opcodes.RETURN);????????????????mv.visitMaxs(2,?0);????????????????mv.visitEnd();????????????????TransformTarget.printSomething();????????????}????????????return?mv;????????}????}}Attacher" data-itemshowtype="0" tab="innerlink" data-linktype="2">執(zhí)行字節(jié)碼修改和轉(zhuǎn)換的類。

        ?classBeingRedefined,?ProtectionDomain?protectionDomain,?byte[]?classfileBuffer)?throws?IllegalClassFormatException?{????????System.out.println("Transforming?"?+?className);????????ClassReader?reader?=?new?ClassReader(classfileBuffer);????????ClassWriter?classWriter?=?new?ClassWriter(ClassWriter.COMPUTE_FRAMES);????????ClassVisitor?classVisitor?=?new?TestClassVisitor(Opcodes.ASM5,?classWriter);????????reader.accept(classVisitor,?ClassReader.SKIP_DEBUG);????????return?classWriter.toByteArray();????}????class?TestClassVisitor?extends?ClassVisitor?implements?Opcodes?{????????TestClassVisitor(int?api,?ClassVisitor?classVisitor)?{????????????super(api,?classVisitor);????????}????????@Override????????public?MethodVisitor?visitMethod(int?access,?String?name,?String?desc,?String?signature,?String[]?exceptions)?{????????????MethodVisitor?mv?=?super.visitMethod(access,?name,?desc,?signature,?exceptions);????????????if?(name.equals("printSomething"))?{????????????????mv.visitCode();????????????????Label?l0?=?new?Label();????????????????mv.visitLabel(l0);????????????????mv.visitLineNumber(19,?l0);????????????????mv.visitFieldInsn(Opcodes.GETSTATIC,?"java/lang/System",?"out",?"Ljava/io/PrintStream;");????????????????mv.visitLdcInsn("bytecode?replaced!");????????????????mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,?"java/io/PrintStream",?"println",?"(Ljava/lang/String;)V",?false);????????????????Label?l1?=?new?Label();????????????????mv.visitLabel(l1);????????????????mv.visitLineNumber(20,?l1);????????????????mv.visitInsn(Opcodes.RETURN);????????????????mv.visitMaxs(2,?0);????????????????mv.visitEnd();????????????????TransformTarget.printSomething();????????????}????????????return?mv;????????}????}}Attacher" data-itemshowtype="0" tab="innerlink" data-linktype="2">public?class?TestTransformer?implements?ClassFileTransformer?{

        ????public?byte[]?transform(ClassLoader?loader,?String?className,?Class?classBeingRedefined,?ProtectionDomain?protectionDomain,?byte[]?classfileBuffer)?throws?IllegalClassFormatException?{
        ????????System.out.println("Transforming?"?+?className);
        ????????ClassReader?reader?=?new?ClassReader(classfileBuffer);
        ????????ClassWriter?classWriter?=?new?ClassWriter(ClassWriter.COMPUTE_FRAMES);
        ????????ClassVisitor?classVisitor?=?new?TestClassVisitor(Opcodes.ASM5,?classWriter);
        ????????reader.accept(classVisitor,?ClassReader.SKIP_DEBUG);
        ????????return?classWriter.toByteArray();
        ????}

        ????class?TestClassVisitor?extends?ClassVisitor?implements?Opcodes?{
        ????????TestClassVisitor(int?api,?ClassVisitor?classVisitor)?{
        ????????????super(api,?classVisitor);
        ????????}

        ????????@Override
        ????????public?MethodVisitor?visitMethod(int?access,?String?name,?String?desc,?String?signature,?String[]?exceptions)?{
        ????????????MethodVisitor?mv?=?super.visitMethod(access,?name,?desc,?signature,?exceptions);
        ????????????if?(name.equals("printSomething"))?{
        ????????????????mv.visitCode();
        ????????????????Label?l0?=?new?Label();
        ????????????????mv.visitLabel(l0);
        ????????????????mv.visitLineNumber(19,?l0);
        ????????????????mv.visitFieldInsn(Opcodes.GETSTATIC,?"java/lang/System",?"out",?"Ljava/io/PrintStream;");
        ????????????????mv.visitLdcInsn("bytecode?replaced!");
        ????????????????mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,?"java/io/PrintStream",?"println",?"(Ljava/lang/String;)V",?false);
        ????????????????Label?l1?=?new?Label();
        ????????????????mv.visitLabel(l1);
        ????????????????mv.visitLineNumber(20,?l1);
        ????????????????mv.visitInsn(Opcodes.RETURN);
        ????????????????mv.visitMaxs(2,?0);
        ????????????????mv.visitEnd();
        ????????????????TransformTarget.printSomething();
        ????????????}
        ????????????return?mv;
        ????????}
        ????}
        }

        ?classBeingRedefined,?ProtectionDomain?protectionDomain,?byte[]?classfileBuffer)?throws?IllegalClassFormatException?{????????System.out.println("Transforming?"?+?className);????????ClassReader?reader?=?new?ClassReader(classfileBuffer);????????ClassWriter?classWriter?=?new?ClassWriter(ClassWriter.COMPUTE_FRAMES);????????ClassVisitor?classVisitor?=?new?TestClassVisitor(Opcodes.ASM5,?classWriter);????????reader.accept(classVisitor,?ClassReader.SKIP_DEBUG);????????return?classWriter.toByteArray();????}????class?TestClassVisitor?extends?ClassVisitor?implements?Opcodes?{????????TestClassVisitor(int?api,?ClassVisitor?classVisitor)?{????????????super(api,?classVisitor);????????}????????@Override????????public?MethodVisitor?visitMethod(int?access,?String?name,?String?desc,?String?signature,?String[]?exceptions)?{????????????MethodVisitor?mv?=?super.visitMethod(access,?name,?desc,?signature,?exceptions);????????????if?(name.equals("printSomething"))?{????????????????mv.visitCode();????????????????Label?l0?=?new?Label();????????????????mv.visitLabel(l0);????????????????mv.visitLineNumber(19,?l0);????????????????mv.visitFieldInsn(Opcodes.GETSTATIC,?"java/lang/System",?"out",?"Ljava/io/PrintStream;");????????????????mv.visitLdcInsn("bytecode?replaced!");????????????????mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,?"java/io/PrintStream",?"println",?"(Ljava/lang/String;)V",?false);????????????????Label?l1?=?new?Label();????????????????mv.visitLabel(l1);????????????????mv.visitLineNumber(20,?l1);????????????????mv.visitInsn(Opcodes.RETURN);????????????????mv.visitMaxs(2,?0);????????????????mv.visitEnd();????????????????TransformTarget.printSomething();????????????}????????????return?mv;????????}????}}Attacher" data-itemshowtype="0" tab="innerlink" data-linktype="2">Attacher

        使用 tools.jar 里方法將 agent 動態(tài)加載到目標 JVM 的類。

        public?class?Attacher?{
        ????public?static?void?main(String[]?args)?throws?AttachNotSupportedException,?IOException,?AgentLoadException,?AgentInitializationException?{

        ????????VirtualMachine?vm?=?VirtualMachine.attach("34242");?//?目標?JVM?pid
        ????????vm.loadAgent("/path/to/agent.jar");
        ????}
        }

        這樣,先啟動 TransformTarget 類,獲取到 pid 后將其傳入 Attacher 里,并指定 agent jar,將 agent attach 到 TransformTarget 中,原來輸出的 “hello” 就變成我們想要修改的 “bytecode replaced!” 了。

        小結(jié)

        掌握了字節(jié)碼的動態(tài)修改技術(shù)后,再回頭看 Btrace 的原理就更清晰了,稍微摸索一下我們也可以實現(xiàn)一個簡版的。

        另外很多大牛實現(xiàn)的各種 Java 性能分析工具的技術(shù)棧也不外如此,了解了這些,未來我們也可以寫出適合自己的工具,至少能對別人的工具進行修改~

        不得不說 Java 的生態(tài)真的非常繁榮,當(dāng)真是博大精深,查閱一個模塊的資料時能總引出一大堆新的概念,永遠有學(xué)不完的新東西。另外,關(guān)注公眾號Java技術(shù)棧,在后臺回復(fù):面試,可以獲取我整理的 Java 系列面試題和答案,非常齊全。








        開工大吉!再發(fā) 10,000 個紅包封面
        2021 年發(fā)生的 10 件技術(shù)大事??!
        23 種設(shè)計模式實戰(zhàn)(很全)
        換掉 Log4j2!tinylog 橫空出世
        再見單身狗!Java 創(chuàng)建對象的 6 種方式
        勁爆!Java 協(xié)程要來了!
        重磅官宣:Redis 對象映射框架來了??!
        別再寫爆爆爆炸類了,試試裝飾器模式!
        程序員精通各種技術(shù)體系,45歲求職難!
        Spring Boot 3.0 M1 發(fā)布,正式棄用 Java 8
        Spring Boot 學(xué)習(xí)筆記,這個太全了!



        關(guān)注Java技術(shù)??锤喔韶?/strong>



        獲取 Spring Boot 實戰(zhàn)筆記!
        瀏覽 50
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            天天综合成人网 | 秋霞电影网院午夜伦不卡A片 | 国产精品v欧美精品v日韩 | 国产日韩欧美三级 | 黑人艹逼视频 | 女人隐私视频黄www | 黄色免费小说在线观看 | 操b国产 亚洲一区二区无码 | 在线中文无码 | 久久加勒比|