天天看點

jvm指令介紹及線上debug工具原理

Java Virtual Machine:

To understand the details of the bytecode, we need to discuss how a Java Virtual Machine (JVM) works regarding the execution of the bytecode. JVM is a platform-independent execution environment that converts Java bytecode into machine language and executes it. A JVM is a stack-based machine. Each thread has a JVM stack which stores frames. A frame is created each time a method is invoked, and consists of an operand stack, an array of local variables, and a reference to the runtime constant pool of the class of the current method.

jvm指令介紹及線上debug工具原理

Stack Based Virtual Machines:

We need to know a little about stack based VM to better understand Java Bytecode. A stack based virtual machine the memory structure where the operands are stored is a stack data structure. Operations are carried out by popping data from the stack, processing them and pushing in back the results in LIFO (Last in First Out) fashion. In a stack based virtual machine, the operation of adding two numbers would usually be carried out in the following manner (where 20, 7, and “result” are the operands):

jvm指令介紹及線上debug工具原理

idea->view->show bytecode(安裝方式Preferences->Plugins->搜“Bytecode Viewer”)

jvm指令介紹及線上debug工具原理

ASMifier asm指令方式

ClassReader reader = new ClassReader(Hello.class.getName());

reader.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(System.out)), ClassReader.SKIP_DEBUG);

靜态方法與非靜态方法的調用核心差別在于,本地變量數組的第一個元素是否是this,比如hello、hi兩個空方法

初步了解操作棧、本地變量數組的“變化過程”

初始的local variables中的值分别為可選的this對象(靜态方法沒有)及方法參數,故可通過在方法開始時讀取local variables擷取方法的入參、修改入參等

在方法結束時傳回值位于棧頂,可以讀取“operand stack”擷取方法的傳回值

有人聽過try catch會比較耗時嗎?通過位元組碼分析一下原因

總結:通過TRYCATCHBLOCK、GOTO、LABEL方式可以方法增加try catch,也可以把異常吃掉等

問題:本例中local variables中index為2的沒有被使用,why?

方法正常傳回是operand stack棧頂為的傳回值

方法的入參存儲在local variables中,靜态方法從0開始、非靜态從1開始

try catch指令,提供了攔截異常的一種方式,甚至吃掉異常

cglib與javassist是進階的位元組碼工具,asm與bcel是低級的位元組碼工具(支援java位元組碼指令),低級的工具會更靈活,故選擇asm來作為分析,asm文檔參見

如何把增強的代碼加入到目前的jvm中?spring已經給了我們答案,但是有兩個疑問:

HelloService$EnhancerByCGLIB$41503a7e,為什麼不是HelloService ?

答:HelloService已經被AppClassLoader加載(通常情況下),不能重複加載相同名稱的class

bean的執行個體的getSuperClass()的值為什麼是HelloService ?

答:HelloService helloService=(HelloService)getBean(“helloService”),如果動态生成的class不是被增強的class子類,強制轉化報異常

AppClassLoader并未暴露defineClass方法,如何加載的增強類?

答:通過反射調用classloader.defineClass即可

故spring本質是對代理類做了一個子類,故除了對“代理類位元組碼”增強外,還需要修改“代理類位元組碼”的parent、及class name

DemoClassVisitor:對方法的增強,入參、傳回值、異常、耗時

ChangeParentClassVisitor:修改類的parent

ChangeNameClassVisitor:修改類名稱

注:asm代碼主要使用了visitor、責任鍊模式,了解了這兩個設計模式代碼讀起來比較容易

ChangeParentClassVisitor、ChangeNameClassVisitor不涉及jvm指令代碼相對簡單,故我們重點介紹DemoClassVisitor,它通過繼承asm的工具類AdviceAdapter,分别實作onMethodEnter、onMethodExit,核心代碼如下:

onMethodEnter中增加如下code:

onMethodEnter中最上面增加如下code:

onMethodExit中最下面增加如下code:

onMethodExit中增加如下code:

原始的HelloService代碼

注意雖然調用的是HelloService,但實際運作是HelloServiceYqf

通過增強,我們擷取導入入參、響應時間(該方法為void故無傳回值)

注釋掉onMethodExit中異常代碼的dup及mv.visitInsn(Opcodes.ATHROW)代碼即可,被攔截的方法必須沒有傳回值,否則需要在代碼裡生成預設值

控制台輸出

注意main開頭的列印代碼未出現在控制台

Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能從本地代碼中解放出來,使之可以用 Java 代碼的方式解決問題。使用 Instrumentation,開發者可以建構一個獨立于應用程式的代理程式(Agent),用來監測和協助運作在 JVM 上的程式,甚至能夠替換和修改某些類的定義。有了這樣的功能,開發者就可以實作更為靈活的運作時虛拟機監控和 Java 類操作了,這樣的特性實際上提供了一種虛拟機級别支援的 AOP 實作方式,使得開發者無需對 JDK 做任何更新和改動,就可以實作某些 AOP 的功能了。

在 Java SE 6 裡面,instrumentation 包被賦予了更強大的功能:啟動後的 instrument、本地代碼(native code)instrument,以及動态改變 classpath 等等。這些改變,意味着 Java 具有了更強的動态控制、解釋能力,它使得 Java 語言變得更加靈活多變。

在 Java SE6 裡面,最大的改變使運作時的 Instrumentation 成為可能。在 Java SE 5 中,Instrument 要求在運作前利用指令行參數或者系統參數來設定代理類,在實際的運作之中,虛拟機在初始化之時(在絕大多數的 Java 類庫被載入之前),instrumentation 的設定已經啟動,并在虛拟機中設定了回調函數,檢測特定類的加載情況,并完成實際工作。但是在實際的很多的情況下,我們沒有辦法在虛拟機啟動之時就為其設定代理,這樣實際上限制了 instrument 的應用。而 Java SE 6 的新特性改變了這種情況,通過 Java Tool API 中的 attach 方式,我們可以很友善地在運作過程中動态地設定加載代理類,以達到 instrumentation 的目的。

偷懶把依賴的jar包也打進去,友善一些

代碼隻用到了DemoClassVisitor,并不需要替換名稱、及父類,因為Java Tool API提供了“直接修改已加載的class位元組碼”的能力,和“類spring”方式不同

VirtualMachine及VirtualMachineDescriptor是非标準包,在windows下是沒有的!

運作方式:先運作agentTarget啟動待增強的jvm、再運作agentTest動态增強

注意是HelloService.sayHi,不是HelloServiceQyf.sayHi

spring的代碼增強的原理,本質是通過classloader反射+asm位元組碼增強,去做一個目标類的子類加載到目前jvm

線上位元組碼工具原理,通過java agent方式動态的修改jvm被增強的class,實作擷取入參、異常、傳回值、耗時等