摘要:什么是字節(jié)碼程序通過(guò)編譯之后生成文件就是字節(jié)碼集合正是有這樣一種中間碼字節(jié)碼,使得等函數(shù)語(yǔ)言只用實(shí)現(xiàn)一個(gè)編譯器即可運(yùn)行在上。
什么是字節(jié)碼?
java程序通過(guò)javac編譯之后生成文件.class就是字節(jié)碼集合,正是有這樣一種中間碼(字節(jié)碼),使得scala/groovy/clojure等函數(shù)語(yǔ)言只用實(shí)現(xiàn)一個(gè)編譯器即可運(yùn)行在JVM上。
看看一段簡(jiǎn)單代碼。
public long getExclusiveTime() { long startTime = System.currentTimeMillis(); System.out.printf("exclusive code"); long endTime = System.currentTimeMillis(); return endTime - startTime; } public class com.blueware.agent.StartAgent {
編譯后通過(guò)命令(javap -c com.blueware.agent.StartAgent)查看,具體含義請(qǐng)參考o(jì)racle
public com.blueware.agent.StartAgent(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."為什么要學(xué)習(xí)字節(jié)碼?":()V 4: return public long getExclusiveTime(); Code: 0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 3: lstore_1 4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #4 // String exclusive code 9: iconst_0 10: anewarray #5 // class java/lang/Object 13: invokevirtual #6 // Method java/io/PrintStream.printf:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; 16: pop 17: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 20: lstore_3 21: lload_3 22: lload_1 23: lsub 24: lreturn }
能了解技術(shù)背后的原理,更容易寫(xiě)出高質(zhì)量代碼;
字節(jié)碼設(shè)計(jì)非常優(yōu)秀,發(fā)展十幾年只僅僅刪除和增加幾個(gè)指令,學(xué)懂之后長(zhǎng)期受益高,如果懂字節(jié)碼再學(xué)習(xí)scala/groovy/clojure會(huì)容易很多;
開(kāi)發(fā)框架、監(jiān)控系統(tǒng)、中間件、語(yǔ)言字節(jié)碼技術(shù)都是必殺技;
字節(jié)碼框架(ASM/Javassist)操作字節(jié)碼框架有很多,具體可以參考博文,下面對(duì)比ASM/Javassist
選項(xiàng) | 優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|---|
ASM | 速度快、代碼量小、功能強(qiáng)大 | 要寫(xiě)字節(jié)碼、學(xué)習(xí)曲線(xiàn)高 |
Javassist | 學(xué)習(xí)簡(jiǎn)單,不用寫(xiě)字節(jié)碼 | 比ASM慢,功能少 |
指的是可以用獨(dú)立于應(yīng)用程序之外的代理(agent)程序,agent程序通過(guò)增強(qiáng)字節(jié)碼動(dòng)態(tài)修改或者新增類(lèi),利用這樣特性可以設(shè)計(jì)出更通用的監(jiān)控、框架、中間件程序,在JVM啟動(dòng)參數(shù)加–javaagent:agent_jar_path/agent.jar即可運(yùn)行(在JDK5及其后續(xù)版本才可以),更多關(guān)于Instrumentation知識(shí)請(qǐng)參考博文
計(jì)算方法執(zhí)行時(shí)間方式直接在代碼開(kāi)始和結(jié)束出打印當(dāng)前時(shí)間,相減即可得到;
實(shí)現(xiàn)一個(gè)動(dòng)態(tài)代理,或者借助Spring/AspectJ等框架;
上面兩種實(shí)現(xiàn)方式都需要修改代碼或者配置文件,下面我要介紹方式不僅不需要修改代碼,而且效率高;
具體實(shí)現(xiàn)方式1.StartAgent類(lèi)必須提供premain方法,代碼如下:
public class StartAgent { //代理程序入口函數(shù) public static void premain(String args, Instrumentation inst) { System.out.println("agent begin"); //添加字節(jié)碼轉(zhuǎn)換器 inst.addTransformer(new PrintTimeTransformer()); System.out.println("agent end"); } }
2.PrintTimeTransformer實(shí)現(xiàn)一個(gè)轉(zhuǎn)換器,代碼如下:
//字節(jié)碼轉(zhuǎn)化器類(lèi) public class PrintTimeTransformer implements ClassFileTransformer { //實(shí)現(xiàn)字節(jié)碼轉(zhuǎn)化接口,一個(gè)小技巧建議實(shí)現(xiàn)接口方法時(shí)寫(xiě)@Override,方便重構(gòu) //loader:定義要轉(zhuǎn)換的類(lèi)加載器,如果是引導(dǎo)加載器,則為 null(在這個(gè)小demo暫時(shí)還用不到) //className:完全限定類(lèi)內(nèi)部形式的類(lèi)名稱(chēng)和中定義的接口名稱(chēng),例如"java.lang.instrument.ClassFileTransformer" //classBeingRedefined:如果是被重定義或重轉(zhuǎn)換觸發(fā),則為重定義或重轉(zhuǎn)換的類(lèi);如果是類(lèi)加載,則為 null //protectionDomain:要定義或重定義的類(lèi)的保護(hù)域 //classfileBuffer:類(lèi)文件格式的輸入字節(jié)緩沖區(qū)(不得修改) //一個(gè)格式良好的類(lèi)文件緩沖區(qū)(轉(zhuǎn)換的結(jié)果),如果未執(zhí)行轉(zhuǎn)換,則返回 null。 @Override public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { //簡(jiǎn)化測(cè)試demo,直接寫(xiě)待修改的類(lèi)(com/blueware/agent/TestTime) if (className != null && className.equals("com/blueware/agent/TestTime")) { //讀取類(lèi)的字節(jié)碼流 ClassReader reader = new ClassReader(classfileBuffer); //創(chuàng)建操作字節(jié)流值對(duì)象,ClassWriter.COMPUTE_MAXS:表示自動(dòng)計(jì)算棧大小 ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS); //接受一個(gè)ClassVisitor子類(lèi)進(jìn)行字節(jié)碼修改 reader.accept(new TimeClassVisitor(writer, className), 8); //返回修改后的字節(jié)碼流 return writer.toByteArray(); } return null; } }
3.TimeClassVisitor類(lèi)訪問(wèn)器,實(shí)現(xiàn)字節(jié)碼修改,代碼如下:
//定義掃描待修改class的visitor,visitor就是訪問(wèn)者模式 public class TimeClassVisitor extends ClassVisitor { private String className; public TimeClassVisitor(ClassVisitor cv, String className) { super(Opcodes.ASM5, cv); this.className = className; } //掃描到每個(gè)方法都會(huì)進(jìn)入,參數(shù)詳情下一篇博文詳細(xì)分析 @Override public MethodVisitor visitMethod(int access, final String name, final String desc, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); final String key = className + name + desc; //過(guò)來(lái)待修改類(lèi)的構(gòu)造函數(shù) if (!name.equals("") && mv != null) { mv = new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) { //方法進(jìn)入時(shí)獲取開(kāi)始時(shí)間 @Override public void onMethodEnter() { //相當(dāng)于com.blueware.agent.TimeUtil.setStartTime("key"); this.visitLdcInsn(key); this.visitMethodInsn(Opcodes.INVOKESTATIC, "com/blueware/agent/TimeUtil", "setStartTime", "(Ljava/lang/String;)V", false); } //方法退出時(shí)獲取結(jié)束時(shí)間并計(jì)算執(zhí)行時(shí)間 @Override public void onMethodExit(int opcode) { //相當(dāng)于com.blueware.agent.TimeUtil.setEndTime("key"); this.visitLdcInsn(key); this.visitMethodInsn(Opcodes.INVOKESTATIC, "com/blueware/agent/TimeUtil", "setEndTime", "(Ljava/lang/String;)V", false); //向棧中壓入類(lèi)名稱(chēng) this.visitLdcInsn(className); //向棧中壓入方法名 this.visitLdcInsn(name); //向棧中壓入方法描述 this.visitLdcInsn(desc); //相當(dāng)于com.blueware.agent.TimeUtil.getExclusiveTime("com/blueware/agent/TestTime","testTime"); this.visitMethodInsn(Opcodes.INVOKESTATIC, "com/blueware/agent/TimeUtil", "getExclusiveTime", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J", false); } }; } return mv; } }
4.TimeClassVisitor記錄時(shí)間幫助類(lèi),代碼如下:
public class TimeUtil { private static Map題記startTimes = new HashMap (); private static Map endTimes = new HashMap (); private TimeUtil() { } public static long getStartTime(String key) { return startTimes.get(key); } public static void setStartTime(String key) { startTimes.put(key, System.currentTimeMillis()); } public static long getEndTime(String key) { return endTimes.get(key); } public static void setEndTime(String key) { endTimes.put(key, System.currentTimeMillis()); } public static long getExclusiveTime(String className, String methodName, String methodDesc) { String key = className + methodName + methodDesc; long exclusive = getEndTime(key) - getStartTime(key); System.out.println(className.replace("/", ".") + "." + methodName + " exclusive:" + exclusive); return exclusive; } }
上面的代碼難免有bug,如果你發(fā)現(xiàn)代碼寫(xiě)的有問(wèn)題,請(qǐng)你幫忙指出,讓我們一起進(jìn)步,讓代碼變的更漂亮和健壯;
順便打點(diǎn)廣告,如果看后對(duì)字節(jié)碼技術(shù)感興趣,歡迎加入我們oneapm,一起做點(diǎn)有意思事情,可直接聯(lián)系我;
完整代碼請(qǐng)?jiān)L問(wèn)github;
下一篇結(jié)合demo再深入研究ClassVisitor
OneAPM 為您提供端到端的 Java 應(yīng)用性能解決方案,我們支持所有常見(jiàn)的 Java 框架及應(yīng)用服務(wù)器,助您快速發(fā)現(xiàn)系統(tǒng)瓶頸,定位異常根本原因。分鐘級(jí)部署,即刻體驗(yàn),Java 監(jiān)控從來(lái)沒(méi)有如此簡(jiǎn)單。想閱讀更多技術(shù)文章,請(qǐng)?jiān)L問(wèn) OneAPM 官方技術(shù)博客,還可以?huà)叽a關(guān)注下方的Java程序性能優(yōu)化公眾號(hào)。
本文轉(zhuǎn)自 OneAPM 官方博客
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/65531.html
摘要:基于局部性原理,計(jì)算機(jī)處理器在設(shè)計(jì)時(shí)做了各種優(yōu)化,比如現(xiàn)代的多級(jí)分支預(yù)測(cè)有良好局部性的程序比局部性差的程序運(yùn)行得更快。目前計(jì)算機(jī)設(shè)計(jì)中,都是以塊頁(yè)為單位管理調(diào)度存儲(chǔ),其實(shí)就是在利用空間局部性來(lái)優(yōu)化性能。 學(xué)過(guò)計(jì)算機(jī)底層原理、了解過(guò)很多架構(gòu)設(shè)計(jì)或者是做過(guò)優(yōu)化的同學(xué),應(yīng)該很熟悉局部性原理。即便是非計(jì)算機(jī)行業(yè)的人,在做各種調(diào)優(yōu)、提效時(shí)也不得不考慮到局部性,只不過(guò)他們不常用局部性一詞。如果...
摘要:我下圖代碼第五行和第九行分別定義了一個(gè)整型變量和一個(gè)整型常量程序員都知道兩者的區(qū)別。下面我們就用將文件反編譯出來(lái)然后深入研究里整型變量和整型常量的區(qū)別。 我下圖代碼第五行和第九行分別定義了一個(gè)整型變量和一個(gè)整型常量: static final int number1 = 512; static int number3 = 545; Java程序員都知道兩者的區(qū)別。 showImg(ht...
摘要:寫(xiě)在前面博客主頁(yè)的江湖背景的江湖背景歡迎關(guān)注點(diǎn)贊收藏留言本文由原創(chuàng),首發(fā)首發(fā)時(shí)間年月日最新更新時(shí)間年月日?qǐng)?jiān)持和努力一定能換來(lái)詩(shī)與遠(yuǎn)方向未見(jiàn)花聞學(xué)習(xí)參考書(shū)籍深入理解計(jì)算機(jī)系統(tǒng)作者水平很有限,如果發(fā)現(xiàn)錯(cuò)誤,請(qǐng)留言轟炸哦萬(wàn)分感謝感謝感謝 ?寫(xiě)在前面 ?博客主頁(yè):kikoking的江湖背景?...
摘要:由虛擬機(jī)加載的類(lèi),被加載到虛擬機(jī)內(nèi)存中之后,虛擬機(jī)會(huì)讀取并執(zhí)行它里面存在的字節(jié)碼指令。虛擬機(jī)中執(zhí)行字節(jié)碼指令的部分叫做執(zhí)行引擎。 什么是Java虛擬機(jī)? 作為一個(gè)Java程序員,我們每天都在寫(xiě)Java代碼,我們寫(xiě)的代碼都是在一個(gè)叫做Java虛擬機(jī)的東西上執(zhí)行的。但是如果要問(wèn)什么是虛擬機(jī),恐怕很多人就會(huì)模棱兩可了。在本文中,我會(huì)寫(xiě)下我對(duì)虛擬機(jī)的理解。因?yàn)槟芰λ?,可能有些地方描述的不夠?..
閱讀 3704·2021-09-02 15:11
閱讀 4682·2021-08-16 10:47
閱讀 1595·2019-08-29 18:35
閱讀 3098·2019-08-28 17:54
閱讀 2878·2019-08-26 11:37
閱讀 1533·2019-08-23 16:51
閱讀 1845·2019-08-23 14:36
閱讀 1840·2019-08-23 14:21