Java反射操作百倍性能優化

歡迎來到啾啾的博客🐱。
記錄學習點滴。分享工作思考和實用技巧,偶爾也分享一些雜談💬。
有很多很多不足的地方,歡迎評論交流,感謝您的閱讀和評論😄。

目錄

  • 引言
  • 避免在性能敏感的熱點代碼中使用反射
  • 緩存反射對象
  • 使用setAccessible(true)
  • 使用MethodHandle
    • 演示
  • 生成字節碼來避免反射

引言

總所周知,反射操作性能開銷相對較大。
然而,一些技巧可以顯著優化反射操作的性能。

資料引用:Java Reflection, 1000x Faster

避免在性能敏感的熱點代碼中使用反射

這是最重要的一條原則。因為反射涉及動態類型解析,會阻礙JVM的即時編譯(JIT)優化。在需要頻繁調用的代碼中,應盡量避免使用反射。

緩存反射對象

反射操作中,Class.forName()、Class.getMethod() 和 Class.getField() 這類查找操作非常耗時。如果需要多次對同一個類或方法進行反射操作,應該將查找到的 Class、Method、Field 和 Constructor 對象緩存起來,避免重復查找。

// 不推薦的方式
public void badReflection(Object obj) throws Exception {Method method = obj.getClass().getMethod("doSomething");method.invoke(obj);
}// 推薦的方式:使用Map緩存Method對象
private final Map<String, Method> methodCache = new ConcurrentHashMap<>();public void goodReflection(Object obj) throws Exception {String className = obj.getClass().getName();Method method = methodCache.computeIfAbsent(className, k -> {try {return obj.getClass().getMethod("doSomething");} catch (NoSuchMethodException e) {throw new RuntimeException(e);}});method.invoke(obj);
}

使用setAccessible(true)

當需要調用非公共(private, protected, default)的成員時,必須先調用 setAccessible(true) 來跳過Java語言的訪問權限檢查。這可以顯著提升反射調用的速度。

使用MethodHandle

從Java 7開始引入的 MethodHandle 提供了一種比反射更現代、性能更好的動態方法調用機制。它與反射不同,是“類型化”的,并且可以更好地被JVM優化。
常用于處理處理在編譯時未知,在運行時才確定的調用。

和反射區別如下:

特性反射 (java.lang.reflect.Method)方法句柄 (java.lang.invoke.MethodHandle)
本質描述方法元數據的信息類指向方法字節碼的直接引用,像函數指針
性能較慢,每次調用都有安全檢查和參數解包/打包開銷首次查找有開銷,后續調用接近原生調用速度,可被JIT優化
類型安全弱類型。invoke(Object, Object…) 接收任意對象,編譯時不檢查,運行時可能拋出類型轉換異常強類型。句柄有明確的 MethodType,調用時如果類型不匹配,編譯或運行時會立即失敗
安全性每次調用都檢查訪問權限僅在創建句柄時檢查一次訪問權限
靈活性API簡單直觀API更復雜,但提供強大的句柄組合與轉換能力
  • MethodHandle:直接指向方法的引用,調用時可以像普通方法一樣,并且可以被JIT編譯器優化。
  • LambdaMetafactory:結合 MethodHandle 使用,可以創建出實現了特定函數式接口的Lambda實例,其性能幾乎等同于直接的方法調用。這種方式在運行時完全避免了反射的開銷。

演示

  • 比如在編寫框架時,需要根據配置文件來調用某個對象的特定方法。

配置文件(config.json)

{"className": "com.example.UserValidator","methodToCall": "validate"
}

我們的程序它在編譯時完全不知道UserValidator這個類,也不知道validate這個方法。
此時可以使用MethodHandle來動態

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;// --- 假設這是用戶編寫的類 ---
class UserValidator {public boolean validate() {System.out.println("UserValidator is validating...");return true;}
}// --- 這是您編寫的框架代碼 ---
public class Framework {public void executeFromConfig(Object instance, String methodName) {System.out.println("Framework is about to call method '" + methodName + "' on instance " + instance.getClass().getSimpleName());try {// 1. 獲取一個查找上下文MethodHandles.Lookup lookup = MethodHandles.lookup();// 2. 動態確定方法的簽名(這里假設是無參,返回boolean)MethodType methodType = MethodType.methodType(boolean.class);// 3. 動態查找方法,得到一個MethodHandle//    這里的 instance.getClass() 和 methodName 都是在運行時才確定的!MethodHandle handle = lookup.findVirtual(instance.getClass(), methodName, methodType);// 4. 調用 handle.invoke() 或 invokeExact()//    這里告訴JVM:請在`instance`這個具體的對象上,執行`handle`所代表的方法。boolean result = (boolean) handle.invoke(instance);System.out.println("Execution result: " + result);} catch (Throwable t) {// 注意:invoke() 和 findVirtual() 都會拋出 Throwablet.printStackTrace();}}public static void main(String[] args) {Framework framework = new Framework();UserValidator userValidatorInstance = new UserValidator();// 框架根據運行時信息(比如配置文件),來調用對象的方法framework.executeFromConfig(userValidatorInstance, "validate");}
}
  • 假設我們有兩個不同的類,但我們想用一個統一的框架來處理它們的某個方法
class Greeter {public String sayHello(String name) {return "Hello, " + name;}
}class Calculator {public int add(int a, int b) {return a + b;}
}public class AdvancedFramework {public static void main(String[] args) throws Throwable {MethodHandles.Lookup lookup = MethodHandles.lookup();// --- 場景一:調用 Greeter 的 sayHello 方法 ---Greeter greeter = new Greeter();// 1. 定義方法類型:(String) -> StringMethodType mtHello = MethodType.methodType(String.class, String.class);// 2. 查找句柄MethodHandle mhHello = lookup.findVirtual(Greeter.class, "sayHello", mtHello);// 3. 調用,第一個參數是實例本身,后面是方法的參數String result1 = (String) mhHello.invoke(greeter, "World");System.out.println("Greeter result: " + result1); // 輸出: Greeter result: Hello, World// --- 場景二:調用 Calculator 的 add 方法 ---Calculator calculator = new Calculator();// 1. 定義方法類型:(int, int) -> intMethodType mtAdd = MethodType.methodType(int.class, int.class, int.class);// 2. 查找句柄MethodHandle mhAdd = lookup.findVirtual(Calculator.class, "add", mtAdd);// 3. 調用int result2 = (int) mhAdd.invoke(calculator, 10, 20);System.out.println("Calculator result: " + result2); // 輸出: Calculator result: 30}
}
  • 適配器模式-綁定參數
    假設你有一個方法對象,但想預先填好它的一個參數,生成一個新的、參數更少的方法對象。這在事件處理等場景中非常有用。
import java.lang.invoke.*;public class BindExample {public void printMessage(String level, String message) {System.out.println("[" + level + "]: " + message);}public static void main(String[] args) throws Throwable {BindExample instance = new BindExample();MethodHandles.Lookup lookup = MethodHandles.lookup();// 原始方法句柄: (BindExample, String, String) -> voidMethodType mt = MethodType.methodType(void.class, String.class, String.class);MethodHandle originalHandle = lookup.findVirtual(BindExample.class, "printMessage", mt);// 我們想創建一個 "INFO" 級別的日志記錄器// 使用 bindTo 綁定第一個參數(實例)和第二個參數(level)MethodHandle infoLogger = originalHandle.bindTo(instance).bindTo("INFO");// 新的句柄類型變成了: (String) -> void// 現在調用新的句柄,只需要提供 message 參數infoLogger.invoke("This is an informational message."); // 輸出: [INFO]: This is an informational message.// 同樣,我們可以創建一個 "ERROR" 級別的日志記錄器MethodHandle errorLogger = originalHandle.bindTo(instance).bindTo("ERROR");errorLogger.invoke("A critical error occurred!"); // 輸出: [ERROR]: A critical error occurred!}
}

生成字節碼來避免反射

對于性能要求極高的場景,例如在框架和庫的開發中,可以通過直接生成字節碼來避免反射。像 ASM、cglib、Byte Buddy 等庫允許在運行時動態創建和修改類。雖然首次生成字節碼的開銷較大,但一旦生成,其執行速度就和普通的Java代碼完全一樣,并且可以被JIT充分優化。

字節碼增強技術內容有點多,感興趣的可以看這篇入門。
深入淺出 Byte Buddy:掌握 Java 運行時代碼操作的利器

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/86850.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/86850.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/86850.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

STM32 _main 里做了什么

Application startup 在大多數嵌入式系統中&#xff0c;進入 main 函數之前需要執行一段初始化序列來設置好系統環境。下圖展示的就是這段初始化序列的默認流程&#xff1a; Figure 1. Default initialization sequence __main is responsible for setting up the memory and…

Java八股文——MySQL「SQL 基礎篇」

NOSQL和SQL的區別&#xff1f; 面試官您好&#xff0c;SQL&#xff08;關系型數據庫&#xff09;和NoSQL&#xff08;非關系型數據庫&#xff09;是當今數據存儲領域的兩大主流陣營。它們之間不是“誰取代誰”的關系&#xff0c;而是兩種完全不同的設計哲學&#xff0c;適用于…

華為OD機考-數字螺旋矩陣(JAVA 2025B卷)

public class RotateMatrix {public static void main(String[] args) {// 順時針螺旋矩陣printMatrixV1();// 逆時針螺旋矩陣//printMatrixV2();}private static void printMatrixV2() {Scanner scan new Scanner(System.in);while(scan.hasNextLine()){String[] line scan.…

【Java工程師面試全攻略】Day7:分布式系統設計面試精要

一、分布式系統概述 分布式系統已成為現代互聯網應用的標配架構&#xff0c;據LinkedIn統計&#xff0c;分布式系統設計能力是高級Java工程師薪資差異的關鍵因素。今天我們將深入解析分布式系統的核心理論和實踐&#xff0c;幫助你掌握面試中的系統設計問題。 二、分布式理論…

Excel處理控件Aspose.Cells教程:在Excel 文件中創建、操作和渲染時間線

您可以使用數據透視表時間軸&#xff0c;而無需調整過濾器來顯示日期——這是一種動態過濾器選項&#xff0c;可讓您輕松按日期/時間進行過濾&#xff0c;并使用滑塊控件放大所需的時間段。Microsoft Excel 允許您通過選擇數據透視表&#xff0c;然后單擊“插入”>“時間軸”…

Python----神經網絡發(神經網絡發展歷程)

年份網絡名稱突出點主要成就論文地址1989LeNet首個現代卷積神經網絡&#xff08;CNN&#xff09;&#xff0c;引入卷積、池化操作手寫數字識別先驅&#xff0c;奠定CNN基礎MNIST Demos on Yann LeCuns website2012AlexNet首次大規模使用深度卷積神經網絡進行圖像識別&#xff1…

mvc與mvp

mvc MVC 架構中&#xff0c;Activity/Fragment&#xff08;作為 View 和 Controller&#xff09;直接持有 Model 或異步任務的引用&#xff0c;當頁面銷毀時&#xff0c;這些長生命周期對象若未正確釋放&#xff0c;會導致 Activity 無法被 GC 回收&#xff0c;形成內存泄漏。…

商業智能中的地圖可視化模板:助力數據高效呈現

引言 在數字化浪潮席卷的當下&#xff0c;數據可視化的重要性愈發凸顯。企業和組織需要從海量的數據中提取有價值的信息&#xff0c;以便做出明智的決策。而可視化地圖組件作為數據可視化的關鍵部分&#xff0c;能夠將數據與地理位置相結合&#xff0c;以直觀、美觀的方式展示…

Opencv 相機標定相關API及原理介紹

Opencv 相機標定相關API及原理介紹 相機標定是計算機視覺中的基礎任務,旨在確定相機的??內參矩陣??、??畸變系數??以及(可選)??外參??(相機相對于世界坐標系的旋轉和平移)。OpenCV提供了完整的相機標定工具鏈,核心函數為cv2.calibrateCamera,其原理基于張正…

深入剖析AI大模型:Prompt 從理論框架到復雜任務的全場景實現

今天我們就Prompt實戰&#xff0c;實現一下復雜場景&#xff0c;通過這些實戰我們就可以更好的理解大模型工作的原理和機制了。我個人覺得Prompt是AI大模型中非常重要的的環節。首先我們還是溫習一下Prompt的框架和基礎原則。然后我們就文本生成、問答任務及復雜任務三個方面分…

Fractal Generative Models論文閱讀筆記與代碼分析

何愷明分型模型這篇文章在二月底上傳到arXiv預出版網站到現在已經過了三個月&#xff0c;當時我也聽說這篇文章時感覺是大有可為&#xff0c;但是幾個月不知道忙啥了&#xff0c;可能錯過很多機會&#xff0c;但是亡羊補牢嘛&#xff0c;而且截至目前&#xff0c;該文章應該也還…

IntelliJ IDEA代碼提示忽略大小寫設置詳解

目錄 前言一、設置步驟1. 打開設置界面2. 進入代碼補全設置3. 配置大小寫敏感選項新版本&#xff08;2023及以上&#xff09;舊版本&#xff08;2022及以下&#xff09; 4. 保存并應用設置 二、效果驗證示例三、注意事項與常見問題1. **適用范圍**2. **版本兼容性**3. **設置未…

Oracle集群OCR磁盤組掉盤問題處理

問題描述 填寫問題的基礎信息。 系統名稱 - IP地址 - 操作系統 HP-UNIX 數據庫 Oracle 11.2.0.4 兩節點RAC 癥狀表現 問題的癥狀表現如下 集群的OCR磁盤組掉了一塊盤(/dev/rdisk/disk52): 查詢集群仲裁盤發現只有兩塊&#xff08;原來是有三塊&#xff09;&#xff…

在WordPress中徹底關閉生成縮略圖的方法

在WordPress中徹底關閉生成縮略圖有多種方法&#xff0c;以下是幾種常見的方法&#xff1a; 方法一&#xff1a;通過修改主題的functions.php文件 登錄WordPress后臺&#xff1a;進入WordPress后臺管理界面。 編輯主題文件&#xff1a; 在左側菜單中找到“外觀”選項&#…

安全-Linux基線核查項點

Linux基線加固/整改 1.限制超級管理員遠程登錄 修改遠程管理程序ssh的配置文件 vi /etc/ssh/sshd_config PermitRootLogin no 重啟sshd服務 systemctl restart sshd 2. 修改默認密碼生存周期 一個好的密碼時間策略如下&#xff1a; vi /etc/login.defs PASS_MAX_DAY 90 最長…

在微信小程序中使用骨架屏

在微信小程序中使用骨架屏可以優化用戶體驗&#xff0c;避免頁面加載時出現白屏現象。以下是詳細的使用方法和注意事項&#xff1a; 使用方法 生成骨架屏代碼&#xff1a; 打開微信開發者工具&#xff0c;進入需要添加骨架屏的頁面。在模擬器面板右下角點擊三個點&#xff0c…

網絡的那些事——初級——OSPF(1)

&#x1f48e;什么是OSPF? OSPF&#xff08;Open Shortest Path First&#xff0c;開放最短路徑優先&#xff09;是一種基于鏈路狀態的內部網關協議&#xff08;IGP&#xff09;&#xff0c;廣泛應用于中大型企業及運營商網絡。其核心設計目標是解決早期協議&#xff08;如RI…

前端導出PDF(適配ios Safari瀏覽器)

目前市面上常用的前端導出PDF庫組合一般為&#xff1a; 1. html2canvas js-pdf 2. html2canvaspdf-lib 3. domtoimagepdf-lib 因本人項目中導出pdf需求為導出30頁及以上的多頁pdf&#xff0c;考慮性能問題&#xff0c;選擇了 html2canvaspdf-lib 及domtoimagepdf-lib兩種方…

physicsnemo開源程序是開源深度學習框架,用于使用最先進的 Physics-ML 方法構建、訓練和微調深度學習模型

?一、軟件介紹 文末提供程序和源碼下載 NVIDIA PhysicsNeMo 是一個開源深度學習框架&#xff0c;用于使用最先進的 SciML 方法構建、訓練、微調和推理物理 AI 模型&#xff0c;以實現 AI4 科學和工程。PhysicsNeMo 提供 python 模塊來構建可擴展和優化的訓練和推理管道&#…

JDBC接口開發指南

1.簡介 JDBC&#xff08;Java Data Base Connectivity,java數據庫連接&#xff09;是一種用于執行SQL語句的Java API&#xff0c;可以為多種關系數據庫提供統一訪問&#xff0c;它由一組用Java語言編寫的類和接口組成。JDBC提供了一種基準&#xff0c;據此可以構建更高級的工具…