JVM深度剖析:一文詳解JVM是如何實(shí)現(xiàn)反射的?
反射是 Java 語言中一個(gè)相當(dāng)重要的特性,它允許正在運(yùn)行的 Java 程序觀測(cè),甚至是修改程序的動(dòng)態(tài)行為。
舉例來說,我們可以通過 Class 對(duì)象枚舉該類中的所有方法,我們還可以通過Method.setAccessible(位于 java.lang.reflect 包,該方法繼承自 AccessibleObject)繞過 Java 語言的訪問權(quán)限,在私有方法所在類之外的地方調(diào)用該方法。
反射在 Java 中的應(yīng)用十分廣泛。開發(fā)人員日常接觸到的 Java 集成開發(fā)環(huán)境(IDE)便運(yùn)用了這一功能:每當(dāng)我們敲入點(diǎn)號(hào)時(shí),IDE 便會(huì)根據(jù)點(diǎn)號(hào)前的內(nèi)容,動(dòng)態(tài)展示可以訪問的字段或者方法。
另一個(gè)日常應(yīng)用則是 Java 調(diào)試器,它能夠在調(diào)試過程中枚舉某一對(duì)象所有字段的值。
(圖中 eclipse 的自動(dòng)提示使用了反射)
在 Web 開發(fā)中,我們經(jīng)常能夠接觸到各種可配置的通用框架。為了保證框架的可擴(kuò)展性,它們往往借助 Java 的反射機(jī)制,根據(jù)配置文件來加載不同的類。舉例來說,Spring 框架的依賴反轉(zhuǎn)(IoC),便是依賴于反射機(jī)制。
然而,我相信不少開發(fā)人員都嫌棄反射機(jī)制比較慢。甚至是甲骨文關(guān)于反射的教學(xué)網(wǎng)頁(yè)[1],也強(qiáng)調(diào)了反射性能開銷大的缺點(diǎn)。
反射調(diào)用的實(shí)現(xiàn)
首先,我們來看看方法的反射調(diào)用,也就是 Method.invoke,是怎么實(shí)現(xiàn)的。
- public final class Method extends Executable {
- ...
- public Object invoke(Object obj, Object... args) throws ... {
- ... //權(quán)限檢查
- MethodAccessor ma = methodAccessor;
- if (ma == null) {
- ma = acquireMethodAccessor();
- }
- return ma.invoke(obj, args);
- }
- }
如果你查閱 Method.invoke 的源代碼,那么你會(huì)發(fā)現(xiàn),它實(shí)際上委派給MethodAccessor 來處理。MethodAccessor 是一個(gè)接口,它有兩個(gè)已有的具體實(shí)現(xiàn):一個(gè)通過本地方法來實(shí)現(xiàn)反射調(diào)用,另一個(gè)則使用了委派模式。為了方便記憶,我便用“本地實(shí)現(xiàn)”和“委派實(shí)現(xiàn)”來指代這兩者。
每個(gè) Method 實(shí)例的第一次反射調(diào)用都會(huì)生成一個(gè)委派實(shí)現(xiàn),它所委派的具體實(shí)現(xiàn)便是一個(gè)本地實(shí)現(xiàn)。本地實(shí)現(xiàn)非常容易理解。當(dāng)進(jìn)入了 Java 虛擬機(jī)內(nèi)部之后,我們便擁有了Method 實(shí)例所指向方法的具體地址。這時(shí)候,反射調(diào)用無非就是將傳入的參數(shù)準(zhǔn)備好,然后調(diào)用進(jìn)入目標(biāo)方法。
- // v0版本
- import java.lang.reflect.Method;
- public class Test {
- public static void target(int i) {
- new Exception("#" + i).printStackTrace();
- }
- public static void main(String[] args) throws Exception {
- Class<?> klass = Class.forName("Test");
- Method method = klass.getMethod("target", int.class);
- method.invoke(null, 0);
- }
- }
- #不同版本的輸出略有不同,這里我使用了Java 10。
- $ java Test
- java.lang.Exception: #0
- at Test.target(Test.java:5)
- at java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke0(Native Methoa t java.base/jdk.internal.reflect.NativeMethodAccessorImpl. .invoke(NativeMethodAt java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.i .invoke(Delegatin
- java.base/java.lang.reflect.Method.invoke(Method.java:564)
- t Test.main(Test.java:131
為了方便理解,我們可以打印一下反射調(diào)用到目標(biāo)方法時(shí)的棧軌跡。在上面的 v0 版本代碼中,我們獲取了一個(gè)指向 Test.target 方法的 Method 對(duì)象,并且用它來進(jìn)行反射調(diào)用。在 Test.target 中,我會(huì)打印出棧軌跡。
可以看到,反射調(diào)用先是調(diào)用了 Method.invoke,然后進(jìn)入委派實(shí)現(xiàn)(DelegatingMethodAccessorImpl),再然后進(jìn)入本地實(shí)現(xiàn)(NativeMethodAccessorImpl),最后到達(dá)目標(biāo)方法。
這里你可能會(huì)疑問,為什么反射調(diào)用還要采取委派實(shí)現(xiàn)作為中間層?直接交給本地實(shí)現(xiàn)不可以么?
其實(shí),Java 的反射調(diào)用機(jī)制還設(shè)立了另一種動(dòng)態(tài)生成字節(jié)碼的實(shí)現(xiàn)(下稱動(dòng)態(tài)實(shí)現(xiàn)),直接使用 invoke 指令來調(diào)用目標(biāo)方法。之所以采用委派實(shí)現(xiàn),便是為了能夠在本地實(shí)現(xiàn)以及動(dòng)態(tài)實(shí)現(xiàn)中切換。
- //動(dòng)態(tài)實(shí)現(xiàn)的偽代碼,這里只列舉了關(guān)鍵的調(diào)用邏輯,其實(shí)它還包括調(diào)用者檢測(cè)、參數(shù)檢測(cè)的字節(jié)碼。
- package jdk.internal.reflect;
- public class GeneratedMethodAccessor1 extends ... {
- @Overrides
- public Object invoke(Object obj, Object[] args) throws ... {
- Test.target((int) args[0]);
- return null;
- }
- }
動(dòng)態(tài)實(shí)現(xiàn)和本地實(shí)現(xiàn)相比,其運(yùn)行效率要快上 20 倍。這是因?yàn)閯?dòng)態(tài)實(shí)現(xiàn)無需經(jīng)過 Java到 C++ 再到 Java 的切換,但由于生成字節(jié)碼十分耗時(shí),僅調(diào)用一次的話,反而是本地實(shí)現(xiàn)要快上 3 到 4 倍。
考慮到許多反射調(diào)用僅會(huì)執(zhí)行一次,Java 虛擬機(jī)設(shè)置了一個(gè)閾值 15(可以通過-Dsun.reflect.inflationThreshold= 來調(diào)整),當(dāng)某個(gè)反射調(diào)用的調(diào)用次數(shù)在 15 之下時(shí),采用本地實(shí)現(xiàn);當(dāng)達(dá)到 15 時(shí),便開始動(dòng)態(tài)生成字節(jié)碼,并將委派實(shí)現(xiàn)的委派對(duì)象切換至動(dòng)態(tài)實(shí)現(xiàn),這個(gè)過程我們稱之為 Inflation。
為了觀察這個(gè)過程,我將剛才的例子更改為下面的 v1 版本。它會(huì)將反射調(diào)用循環(huán) 20 次。
- // v1版本
- import java.lang.reflect.Method;
- public class Test {
- public static void target(int i) {
- new Exception("#" + i).printStackTrace();
- }
- public static void main(String[] args) throws Exception {
- Class<?> klass = Class.forName("Test");
- Method method = klass.getMethod("target", int.class);
- for (int i = 0; i < 20; i++) {
- method.invoke(null, i);
- }
- }
- }
- #使用-verbose:class打印加載的類
- $ java -verbose:class Test
- ...
- java.lang.Exception: #14
- at Test.target(Test.java:5)
- at java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke0(Native Methoat java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke(NativeMethodAat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl .invoke(Delegatinat java.base/java.lang.reflect.Method.invoke(Method.java:564)
- at Test.main(Test.java:12)
- [0.158s][info][class,load] ...
- ...
- [0.160s][info][class,load] jdk.internal.reflect.GeneratedMethodAccessor1 source: __JVM_Djava.lang.Exception: #15
- at Test.target(Test.java:5)
- at java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke0(Native Methodat java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke(NativeMethodAcat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl .invoke(Delegatingat java.base/java.lang.reflect.Method.invoke(Method.java:564)
- at Test.main(Test.java:12)
- java.lang.Exception: #16
- at Test.target(Test.java:5)
- at jdk.internal.reflect.GeneratedMethodAccessor1 .invoke(Unknown Source)
- at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl .invoke(Delegatingat java.base/java.lang.reflect.Method.invoke(Method.java:564)
- at Test.main(Test.java:12)
- ...
可以看到,在第 15 次(從 0 開始數(shù))反射調(diào)用時(shí),我們便觸發(fā)了動(dòng)態(tài)實(shí)現(xiàn)的生成。這時(shí)候,Java 虛擬機(jī)額外加載了不少類。其中,最重要的當(dāng)屬GeneratedMethodAccessor1(第 30 行)。并且,從第 16 次反射調(diào)用開始,我們便切換至這個(gè)剛剛生成的動(dòng)態(tài)實(shí)現(xiàn)(第 40 行)。
反射調(diào)用的 Inflation 機(jī)制是可以通過參數(shù)(-Dsun.reflect.noInflation=true)來關(guān)閉的。這樣一來,在反射調(diào)用一開始便會(huì)直接生成動(dòng)態(tài)實(shí)現(xiàn),而不會(huì)使用委派實(shí)現(xiàn)或者本地實(shí)現(xiàn)。
反射調(diào)用的開銷
下面,我們便來拆解反射調(diào)用的性能開銷。
在剛才的例子中,我們先后進(jìn)行了 Class.forName,Class.getMethod 以及Method.invoke 三個(gè)操作。其中,Class.forName 會(huì)調(diào)用本地方法,Class.getMethod則會(huì)遍歷該類的公有方法。如果沒有匹配到,它還將遍歷父類的公有方法??上攵?,這兩個(gè)操作都非常費(fèi)時(shí)。
值得注意的是,以 getMethod 為代表的查找方法操作,會(huì)返回查找得到結(jié)果的一份拷貝。因此,我們應(yīng)當(dāng)避免在熱點(diǎn)代碼中使用返回 Method 數(shù)組的 getMethods 或者getDeclaredMethods 方法,以減少不必要的堆空間消耗。
在實(shí)踐中,我們往往會(huì)在應(yīng)用程序中緩存 Class.forName 和 Class.getMethod 的結(jié)果。因此,下面我就只關(guān)注反射調(diào)用本身的性能開銷。
為了比較直接調(diào)用和反射調(diào)用的性能差距,我將前面的例子改為下面的 v2 版本。它會(huì)將反射調(diào)用循環(huán)二十億次。此外,它還將記錄下每跑一億次的時(shí)間。
我將取最后五個(gè)記錄的平均值,作為預(yù)熱后的峰值性能。(注:這種性能評(píng)估方式并不嚴(yán)謹(jǐn),我會(huì)在專欄的第三部分介紹如何用 JMH 來測(cè)性能。)
在我這個(gè)老筆記本上,一億次直接調(diào)用耗費(fèi)的時(shí)間大約在 120ms。這和不調(diào)用的時(shí)間是一致的。其原因在于這段代碼屬于熱循環(huán),同樣會(huì)觸發(fā)即時(shí)編譯。并且,即時(shí)編譯會(huì)將對(duì)Test.target 的調(diào)用內(nèi)聯(lián)進(jìn)來,從而消除了調(diào)用的開銷。
- // v2版本
- mport java.lang.reflect.Method;
- public class Test {
- public static void target(int i) {
- //空方法
- }
- public static void main(String[] args) throws Exception {
- Class<?> klass = Class.forName("Test");
- Method method = klass.getMethod("target", int.class);
- long current = System.currentTimeMillis();
- for (int i = 1; i <= 2_000_000_000; i++) {
- if (i % 100_000_000 == 0) {
- long temp = System.currentTimeMillis();
- System.out.println(temp - current);
- current = temp;
- }
- method.invoke(null, 128);
- }
- }
- }
下面我將以 120ms 作為基準(zhǔn),來比較反射調(diào)用的性能開銷。
由于目標(biāo)方法 Test.target 接收一個(gè) int 類型的參數(shù),因此我傳入 128 作為反射調(diào)用的參數(shù),測(cè)得的結(jié)果約為基準(zhǔn)的 2.7 倍。我們暫且不管這個(gè)數(shù)字是高是低,先來看看在反射調(diào)用之前字節(jié)碼都做了什么。
- aload_2 //加載Method對(duì)象
- aconst_null //反射調(diào)用的第一個(gè)參數(shù)null
- iconst_1
- anewarray Object //生成一個(gè)長(zhǎng)度為1的Object數(shù)組
- dup
- iconst_0
- sipush 128
- invokestatic Integer.valueOf //將128自動(dòng)裝箱成Integer73: aastore //存入Object數(shù)組中
- invokevirtual Method.invoke //反射調(diào)用
這里我截取了循環(huán)中反射調(diào)用編譯而成的字節(jié)碼。可以看到,這段字節(jié)碼除了反射調(diào)用外,還額外做了兩個(gè)操作。
- 由于 Method.invoke 是一個(gè)變長(zhǎng)參數(shù)方法,在字節(jié)碼層面它的最后一個(gè)參數(shù)會(huì)是Object 數(shù)組(感興趣的同學(xué)私下可以用 javap 查看)。Java 編譯器會(huì)在方法調(diào)用處生成一個(gè)長(zhǎng)度為傳入?yún)?shù)數(shù)量的 Object 數(shù)組,并將傳入?yún)?shù)一一存儲(chǔ)進(jìn)該數(shù)組中。
- 由于 Object 數(shù)組不能存儲(chǔ)基本類型,Java 編譯器會(huì)對(duì)傳入的基本類型參數(shù)進(jìn)行自動(dòng)裝箱。
這兩個(gè)操作除了帶來性能開銷外,還可能占用堆內(nèi)存,使得 GC 更加頻繁。(如果你感興趣的話,可以用虛擬機(jī)參數(shù) -XX:+PrintGC 試試。)那么,如何消除這部分開銷呢?
關(guān)于第二個(gè)自動(dòng)裝箱,Java 緩存了 [-128, 127] 中所有整數(shù)所對(duì)應(yīng)的 Integer 對(duì)象。當(dāng)需要自動(dòng)裝箱的整數(shù)在這個(gè)范圍之內(nèi)時(shí),便返回緩存的 Integer,否則需要新建一個(gè) Integer對(duì)象。
因此,我們可以將這個(gè)緩存的范圍擴(kuò)大至覆蓋 128(對(duì)應(yīng)參數(shù)-Djava.lang.Integer.IntegerCache.high=128),便可以避免需要新建 Integer 對(duì)象的場(chǎng)景。
或者,我們可以在循環(huán)外緩存 128 自動(dòng)裝箱得到的 Integer 對(duì)象,并且直接傳入反射調(diào)用中。這兩種方法測(cè)得的結(jié)果差不多,約為基準(zhǔn)的 1.8 倍。
現(xiàn)在我們?cè)倩貋砜纯吹谝粋€(gè)因變長(zhǎng)參數(shù)而自動(dòng)生成的 Object 數(shù)組。既然每個(gè)反射調(diào)用對(duì)應(yīng)的參數(shù)個(gè)數(shù)是固定的,那么我們可以選擇在循環(huán)外新建一個(gè) Object 數(shù)組,設(shè)置好參數(shù),并直接交給反射調(diào)用。改好的代碼可以參照文稿中的 v3 版本。
- // v3版本
- import java.lang.reflect.Method;
- public class Test {
- public static void target(int i) {
- //空方法
- }
- public static void main(String[] args) throws Exception {
- Class<?> klass = Class.forName("Test");
- Method method = klass.getMethod("target", int.class);
- Object[] arg = new Object[1];
- //在循環(huán)外構(gòu)造參數(shù)數(shù)組
- arg[0] = 128;
- long current = System.currentTimeMillis();
- for (int i = 1; i <= 2_000_000_000; i++) {
- if (i % 100_000_000 == 0) {
- long temp = System.currentTimeMillis();
- System.out.println(temp - current);
- current = temp;
- }
- method.invoke(null, arg);
- }
- }
- }
測(cè)得的結(jié)果反而更糟糕了,為基準(zhǔn)的 2.9 倍。這是為什么呢?
如果你在上一步解決了自動(dòng)裝箱之后查看運(yùn)行時(shí)的 GC 狀況,你會(huì)發(fā)現(xiàn)這段程序并不會(huì)觸發(fā) GC。其原因在于,原本的反射調(diào)用被內(nèi)聯(lián)了,從而使得即時(shí)編譯器中的逃逸分析將原本新建的 Object 數(shù)組判定為不逃逸的對(duì)象。
如果一個(gè)對(duì)象不逃逸,那么即時(shí)編譯器可以選擇棧分配甚至是虛擬分配,也就是不占用堆空間。具體我會(huì)在本專欄的第二部分詳細(xì)解釋。
如果在循環(huán)外新建數(shù)組,即時(shí)編譯器無法確定這個(gè)數(shù)組會(huì)不會(huì)中途被更改,因此無法優(yōu)化掉訪問數(shù)組的操作,可謂是得不償失。
到目前為止,我們的最好記錄是 1.8 倍。那能不能再進(jìn)一步提升呢?
剛才我曾提到,可以關(guān)閉反射調(diào)用的 Inflation 機(jī)制,從而取消委派實(shí)現(xiàn),并且直接使用動(dòng)態(tài)實(shí)現(xiàn)。此外,每次反射調(diào)用都會(huì)檢查目標(biāo)方法的權(quán)限,而這個(gè)檢查同樣可以在 Java 代碼里關(guān)閉,在關(guān)閉了這兩項(xiàng)機(jī)制之后,也就得到了我們的 v4 版本,它測(cè)得的結(jié)果約為基準(zhǔn)的1.3 倍。
- // v4版本
- import java.lang.reflect.Method;
- //在運(yùn)行指令中添加如下兩個(gè)虛擬機(jī)參數(shù):
- // -Djava.lang.Integer.IntegerCache.high=128
- // -Dsun.reflect.noInflation=true
- public class Test {
- public static void target(int i) {
- //空方法
- }
- public static void main(String[] args) throws Exception {
- Class<?> klass = Class.forName("Test");
- Method method = klass.getMethod("target", int.class);
- method.setAccessible(true);
- //關(guān)閉權(quán)限檢查
- long current = System.currentTimeMillis();
- for (int i = 1; i <= 2_000_000_000; i++) {
- if (i % 100_000_000 == 0) {
- long temp = System.currentTimeMillis();
- System.out.println(temp - current);
- current = temp;
- }
- method.invoke(null, 128);
- }
- }
- }
到這里,我們基本上把反射調(diào)用的水分都榨干了。接下來,我來把反射調(diào)用的性能開銷給提回去。
首先,在這個(gè)例子中,之所以反射調(diào)用能夠變得這么快,主要是因?yàn)榧磿r(shí)編譯器中的方法內(nèi)聯(lián)。在關(guān)閉了 Inflation 的情況下,內(nèi)聯(lián)的瓶頸在于 Method.invoke 方法中對(duì)MethodAccessor.invoke 方法的調(diào)用。
我會(huì)在后面的文章中介紹方法內(nèi)聯(lián)的具體實(shí)現(xiàn),這里先說個(gè)結(jié)論:在生產(chǎn)環(huán)境中,我們往往擁有多個(gè)不同的反射調(diào)用,對(duì)應(yīng)多個(gè) GeneratedMethodAccessor,也就是動(dòng)態(tài)實(shí)現(xiàn)。
由于 Java 虛擬機(jī)的關(guān)于上述調(diào)用點(diǎn)的類型 profile(注:對(duì)于 invokevirtual 或者invokeinterface,Java 虛擬機(jī)會(huì)記錄下調(diào)用者的具體類型,我們稱之為類型 profile)無法同時(shí)記錄這么多個(gè)類,因此可能造成所測(cè)試的反射調(diào)用沒有被內(nèi)聯(lián)的情況。
- // v5版本
- import java.lang.reflect.Method;
- public class Test {
- public static void target(int i) {
- //空方法
- }
- public static void main(String[] args) throws Exception {
- Class<?> klass = Class.forName("Test");
- Method method = klass.getMethod("target", int.class);
- method.setAccessible(true);
- //關(guān)閉權(quán)限檢查
- polluteProfile();
- long current = System.currentTimeMillis();
- for (int i = 1; i <= 2_000_000_000; i++) {
- if (i % 100_000_000 == 0) {
- long temp = System.currentTimeMillis();
- System.out.println(temp - current);
- current = temp;
- }
- method.invoke(null, 128);
- }
- }
- public static void polluteProfile() throws Exception {
- Method method1 = Test.class.getMethod("target1", int.class);
- Method method2 = Test.class.getMethod("target2", int.class);
- for (int i = 0; i < 2000; i++) {
- method1.invoke(null, 0);
- method2.invoke(null, 0);
- }
- }
- public static void target1(int i) {
- }
- public static void target2(int i) {
- }
- }
在上面的 v5 版本中,我在測(cè)試循環(huán)之前調(diào)用了 polluteProfile 的方法。該方法將反射調(diào)用另外兩個(gè)方法,并且循環(huán)上 2000 遍。
而測(cè)試循環(huán)則保持不變。測(cè)得的結(jié)果約為基準(zhǔn)的 6.7 倍。也就是說,只要誤擾了Method.invoke 方法的類型 profile,性能開銷便會(huì)從 1.3 倍上升至 6.7 倍。
之所以這么慢,除了沒有內(nèi)聯(lián)之外,另外一個(gè)原因是逃逸分析不再起效。這時(shí)候,我們便可以采用剛才 v3 版本中的解決方案,在循環(huán)外構(gòu)造參數(shù)數(shù)組,并直接傳遞給反射調(diào)用。這樣子測(cè)得的結(jié)果約為基準(zhǔn)的 5.2 倍。
除此之外,我們還可以提高 Java 虛擬機(jī)關(guān)于每個(gè)調(diào)用能夠記錄的類型數(shù)目(對(duì)應(yīng)虛擬機(jī)參數(shù) -XX:TypeProfileWidth,默認(rèn)值為 2,這里設(shè)置為 3)。最終測(cè)得的結(jié)果約為基準(zhǔn)的2.8 倍,盡管它和原本的 1.3 倍還有一定的差距,但總算是比 6.7 倍好多了。
總結(jié)與實(shí)踐
在默認(rèn)情況下,方法的反射調(diào)用為委派實(shí)現(xiàn),委派給本地實(shí)現(xiàn)來進(jìn)行方法調(diào)用。在調(diào)用超過15 次之后,委派實(shí)現(xiàn)便會(huì)將委派對(duì)象切換至動(dòng)態(tài)實(shí)現(xiàn)。這個(gè)動(dòng)態(tài)實(shí)現(xiàn)的字節(jié)碼是自動(dòng)生成的,它將直接使用 invoke 指令來調(diào)用目標(biāo)方法。
方法的反射調(diào)用會(huì)帶來不少性能開銷,原因主要有三個(gè):變長(zhǎng)參數(shù)方法導(dǎo)致的 Object 數(shù)組,基本類型的自動(dòng)裝箱、拆箱,還有最重要的方法內(nèi)聯(lián)。
本文的實(shí)踐環(huán)節(jié),你可以將最后一段代碼中 polluteProfile 方法的兩個(gè) Method 對(duì)象,都改成獲取名字為“target”的方法。請(qǐng)問這兩個(gè)獲得的 Method 對(duì)象是同一個(gè)嗎(==)?他們 equal 嗎(.equals(…))?對(duì)我們的運(yùn)行結(jié)果有什么影響?
- import java.lang.reflect.Method;
- public class Test {
- public static void target(int i) {
- //空方法
- }
- public static void main(String[] args) throws Exception {
- Class<?> klass = Class.forName("Test");
- Method method = klass.getMethod("target", int.class);
- method.setAccessible(true);
- //關(guān)閉權(quán)限檢查
- polluteProfile();
- long current = System.currentTimeMillis();
- for (int i = 1; i <= 2_000_000_000; i++) {
- if (i % 100_000_000 == 0) {
- long temp = System.currentTimeMillis();
- System.out.println(temp - current);
- current = temp;
- }
- method.invoke(null, 128);
- }
- }
- public static void polluteProfile() throws Exception {
- Method method1 = Test.class.getMethod("target", int.class);
- Method method2 = Test.class.getMethod("target", int.class);
- for (int i = 0; i < 2000; i++) {
- method1.invoke(null, 0);
- method2.invoke(null, 0);
- }
- }
- public static void target1(int i) {
- }
- public static void target2(int i) {
- }
- }