久久福利_99r_国产日韩在线视频_直接看av的网站_中文欧美日韩_久久一

您的位置:首頁技術(shù)文章
文章詳情頁

詳解Java動態(tài)字節(jié)碼技術(shù)

瀏覽:2日期:2022-08-11 16:49:39
目錄對 Debug 的好奇ASM動態(tài)生成字節(jié)碼ASM 框架常用方法InstrumentJVM TI介紹Agent使用agent生成代碼實(shí)現(xiàn)被修改的類AgentAttacher小結(jié)對 Debug 的好奇

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

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

詳解Java動態(tài)字節(jié)碼技術(shù)

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

隨著對 Java 的愈加熟悉,我也了解了反射、字節(jié)碼等技術(shù),直到前些天的周會分享,有位同事分享了 Btrace 的使用和實(shí)現(xiàn),提到了 Java 的 ASM 框架和 JVM TI 接口。 Btrace 修改代碼能力的實(shí)現(xiàn)與 Debug 的 Evaluate 有很多相似之處,這大大吸引了我。分享就像一個(gè)引子,從中學(xué)到的東西只是皮毛,要了解它還是要自己研究。于是自己查看資料并寫代碼學(xué)習(xí)了下其具體實(shí)現(xiàn)。

ASM

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

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

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

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

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

ASM 框架

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

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

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

詳解Java動態(tài)字節(jié)碼技術(shù)

常用方法

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

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

而 ClassWriter 接口繼承了 ClassVisitor 接口,我們在實(shí)例化類訪問器時(shí),將 ClassWriter “注入” 到里面,以實(shí)現(xiàn)對類寫入的聲明。

Instrument

介紹

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

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

使用

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

JVM TI

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

介紹

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

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

Agent

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

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

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

使用

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

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

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

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

agent生成

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

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

<build> <plugins><plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration><archive> <manifestEntries><Agent-Class>asm.TestAgent</Agent-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes><Manifest-Version>1.0</Manifest-Version><Permissions>all-permissions</Permissions> </manifestEntries></archive><descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs> </configuration></plugin> </plugins></build>

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

代碼實(shí)現(xiàn)

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

被修改的類

TransformTarget 是要被修改的目標(biāo)類,正常執(zhí)行時(shí),它會三秒輸出一次 “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。

入口類,也是代理的 Agent-Class。

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!');} }}

執(zhí)行字節(jié)碼修改和轉(zhuǎn)換的類。

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);}@Overridepublic 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

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

public class Attacher { public static void main(String[] args) throws AttachNotSupportedException, IOException, AgentLoadException, AgentInitializationException {VirtualMachine vm = VirtualMachine.attach('34242'); // 目標(biāo) JVM pidvm.loadAgent('/path/to/agent.jar'); }}

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

詳解Java動態(tài)字節(jié)碼技術(shù)

小結(jié)

掌握了字節(jié)碼的動態(tài)修改技術(shù)后,再回頭看 Btrace 的原理就更清晰了,稍微摸索一下我們也可以實(shí)現(xiàn)一個(gè)簡版的。另外很多大牛實(shí)現(xiàn)的各種 Java 性能分析工具的技術(shù)棧也不外如此,了解了這些,未來我們也可以寫出適合自己的工具,至少能對別人的工具進(jìn)行修改~

不得不說 Java 的生態(tài)真的非常繁榮,當(dāng)真是博大精深,查閱一個(gè)模塊的資料時(shí)能總引出一大堆新的概念,永遠(yuǎn)有學(xué)不完的新東西。

以上就是詳解Java動態(tài)字節(jié)碼技術(shù)的詳細(xì)內(nèi)容,更多關(guān)于Java動態(tài)字節(jié)碼技術(shù)的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 黄色国产在线看 | 可以在线观看的av网站 | 久草在线视频免费播放 | 蜜桃视频在线观看www社区 | 精品少妇一区二区三区日产乱码 | 国产一区久久精品 | 最新午夜 | 精品国产青草久久久久福利 | 一级欧美日韩 | 狠狠人人 | 伊人网av| 成人不卡视频 | 99爱在线观看 | 国产精品美女久久久久久久网站 | 日本三级在线观看中文字 | 日韩一级 | 99精品欧美一区二区三区 | 国产最新网站 | 午夜影院免费体验区 | 国产一区二区在线播放 | 国产精品久久九九 | 久久九 | 99久久夜色精品国产亚洲1000部 | а天堂中文最新一区二区三区 | 色婷婷在线播放 | 一级女性全黄久久生活片免费 | 成人在线三级 | 精品一二区 | 欧美日韩国产一区二区三区 | 日韩国产欧美视频 | www.久久.com| 国产精品1区2区3区 欧美 中文字幕 | 欧美综合一区二区 | 欧美性一区二区三区 | 国产情侣激情 | 久久午夜综合久久 | 国产欧美日韩在线 | 久久永久视频 | 欧美一级片在线观看 | 日韩免费 | 羞羞视频在线免费 |