电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

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

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

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

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

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

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

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

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

ASM

實(shí)現(xiàn) Evaluate 要解決的第一個問題就是怎么改變原有代碼的行為,它的實(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)大的一個,使用它可以動態(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 將對代碼的讀取和操作都包裝成一個訪問者,在解析 JVM 加載到的字節(jié)碼時調(diào)用。

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

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

Instrument

介紹

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

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

使用

要使用 instrument 的類修改功能,我們需要實(shí)現(xiàn)它的ClassFileTransformer接口定義一個類文件轉(zhuǎn)換器。它唯一的一個transform()方法會在類文件被加載時調(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 提供的一個非常強(qiáng)大的對 JVM 操作的工具接口,通過這個接口,我們可以實(shí)現(xiàn)對 JVM 多種組件的操作,從JVMTM Tool Interface這里我們認(rèn)識到 JVM TI 的強(qiáng)大,它包括了對虛擬機(jī)堆內(nèi)存、類、線程等各個方面的管理接口。

JVM TI 通過事件機(jī)制,通過接口注冊各種事件勾子,在 JVM 事件觸發(fā)時同時觸發(fā)預(yù)定義的勾子,以實(shí)現(xiàn)對各個 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 時啟動。

這時再來思考 Debug 的實(shí)現(xiàn),我們在啟動被 Debug 的 JVM 時,必須添加參數(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)于一個通道,幫我們翻譯 JDI 指令到 JVM TI,最底層的 JVM TI 最終實(shí)現(xiàn)對 JVM 的操作。

使用

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

而要實(shí)現(xiàn)代碼的修改,我們需要實(shí)現(xiàn)一個 instrument agent,它可以通過在一個類里添加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)類的重定義。

另外往一個正在運(yù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 實(shí)現(xiàn)還需要引用一些其他類庫時,還需要將這些類庫都打包到此 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>

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

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

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

被修改的類

TransformTarget 是要被修改的目標(biāo)類,正常執(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。

入口類,也是代理的 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)一個簡版的。另外很多大牛實(shí)現(xiàn)的各種 Java 性能分析工具的技術(shù)棧也不外如此,了解了這些,未來我們也可以寫出適合自己的工具,至少能對別人的工具進(jìn)行修改~

不得不說 Java 的生態(tài)真的非常繁榮,當(dāng)真是博大精深,查閱一個模塊的資料時能總引出一大堆新的概念,永遠(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)文章:
主站蜘蛛池模板: 注塑模具_塑料模具_塑胶模具_范仕达【官网】_东莞模具设计与制造加工厂家 | vr安全体验馆|交通安全|工地安全|禁毒|消防|安全教育体验馆|安全体验教室-贝森德(深圳)科技 | 重庆磨床过滤机,重庆纸带过滤机,机床伸缩钣金,重庆机床钣金护罩-重庆达鸿兴精密机械制造有限公司 | 选宝石船-陆地水上开采「精选」色选机械设备-青州冠诚重工机械有限公司 | 水成膜泡沫灭火剂_氟蛋白泡沫液_河南新乡骏华消防科技厂家 | 环讯传媒,永康网络公司,永康网站建设,永康小程序开发制作,永康网站制作,武义网页设计,金华地区网站SEO优化推广 - 永康市环讯电子商务有限公司 | 特种电缆厂家-硅橡胶耐高温电缆-耐低温补偿导线-安徽万邦特种电缆有限公司 | 立式_复合式_壁挂式智能化电伴热洗眼器-上海达傲洗眼器生产厂家 理化生实验室设备,吊装实验室设备,顶装实验室设备,实验室成套设备厂家,校园功能室设备,智慧书法教室方案 - 东莞市惠森教学设备有限公司 | 郑州巴特熔体泵有限公司专业的熔体泵,熔体齿轮泵与换网器生产厂家 | 空压机网_《压缩机》杂志| 西门子伺服电机维修,西门子电源模块维修,西门子驱动模块维修-上海渠利 | 螺纹三通快插接头-弯通快插接头-宁波舜驰气动科技有限公司 | 一航网络-软件测评官网| 影像测量仪_三坐标测量机_一键式二次元_全自动影像测量仪-广东妙机精密科技股份有限公司 | 英国公司注册-新加坡公司注册-香港公司开户-离岸公司账户-杭州商标注册-杭州优创企业 | 奇酷教育-Python培训|UI培训|WEB大前端培训|Unity3D培训|HTML5培训|人工智能培训|JAVA开发的教育品牌 | 金属抛光机-磁悬浮抛光机-磁力研磨机-磁力清洗机 - 苏州冠古科技 | 不锈钢拉手厂家|浴室门拉手厂家|江门市蓬江区金志翔五金制品有限公司 | 机械加工_绞车配件_立式离心机_减速机-洛阳三永机械厂 | 商标转让-商标注册-商标查询-软著专利服务平台 - 赣江万网 | ET3000双钳形接地电阻测试仪_ZSR10A直流_SXJS-IV智能_SX-9000全自动油介质损耗测试仪-上海康登 | 深圳VI设计-画册设计-LOGO设计-包装设计-品牌策划公司-[智睿画册设计公司] | 塑胶跑道_学校塑胶跑道_塑胶球场_运动场材料厂家_中国塑胶跑道十大生产厂家_混合型塑胶跑道_透气型塑胶跑道-广东绿晨体育设施有限公司 | 环讯传媒,永康网络公司,永康网站建设,永康小程序开发制作,永康网站制作,武义网页设计,金华地区网站SEO优化推广 - 永康市环讯电子商务有限公司 | 德州网站制作 - 网站建设设计 - seo排名优化 -「两山建站」 | 减速机三参数组合探头|TSM803|壁挂式氧化锆分析仪探头-安徽鹏宸电气有限公司 | 除湿机|工业除湿机|抽湿器|大型地下室车间仓库吊顶防爆除湿机|抽湿烘干房|新风除湿机|调温/降温除湿机|恒温恒湿机|加湿机-杭州川田电器有限公司 | PCB厂|线路板厂|深圳线路板厂|软硬结合板厂|电路板生产厂家|线路板|深圳电路板厂家|铝基板厂家|深联电路-专业生产PCB研发制造 | 全自动包装机_灌装机生产厂家-迈驰包装设备有限公司 | 代理记账_公司起名核名_公司注册_工商注册-睿婕实业有限公司 | 杜甫仪器官网|实验室平行反应器|升降水浴锅|台式低温循环泵 | 西安展台设计搭建_西安活动策划公司_西安会议会场布置_西安展厅设计西安旭阳展览展示 | 高铝矾土熟料_细粉_骨料_消失模_铸造用铝矾土_铝酸钙粉—嵩峰厂家 | 膜结构车棚|上海膜结构车棚|上海车棚厂家|上海膜结构公司 | 不锈钢水管-不锈钢燃气管-卫生级不锈钢管件-不锈钢食品级水管-广东双兴新材料集团有限公司 | 洁净实验室工程-成都手术室净化-无尘车间装修-四川华锐净化公司-洁净室专业厂家 | 青岛成人高考_山东成考报名网 | 金属检测机_金属分离器_检针验针机_食品药品金属检探测仪器-广东善安科技 | 常州翔天实验仪器厂-恒温振荡器-台式恒温振荡器-微量血液离心机 恒温恒湿箱(药品/保健品/食品/半导体/细菌)-兰贝石(北京)科技有限公司 | 礼至家居-全屋定制家具_一站式全屋整装_免费量房设计报价 | 蜗轮丝杆升降机-螺旋升降机-丝杠升降机厂家-润驰传动 |