Java 平台一直以其安全性與穩定性著稱,而這個優勢來自於持續不斷的改進與優化。為了要加強 Java 平台的安全性,自 JDK 24 的 JEP 498 起,每當我們在使用 sun.misc.Unsafe
中已棄用的記憶體存取方法時,都會產生警告提醒。
身為淘汰計畫中的一環,本功能是繼 JDK 23 中 JEP 471 之後的延續性改進,以藉此逐步淘汰不安全的記憶體存取方法,並引導開發者使用更安全、更標準化的 API。它不僅關係到平台的安全性提升,更攸關到許多仰賴這些 API 的函式庫與應用程式。本文會介紹這項變革的背景、目的與實施方式。
目錄
前言
如同老喬之前在 JEP 471 棄用 sun.misc.Unsafe
中的記憶體存取方法中提到的,在 2002 年 Java 發展的早期階段,為了滿足某些特殊的效能需求,平台引入了sun.misc.Unsafe
類別。
它提供了一系列讀寫記憶體的低階方法,包括使用原始指標讀寫 Java 物件、在記憶體間進行複製,以及分配與釋放原生記憶體等功能,讓開發者能夠直接存取和操作記憶體。
Unsafe
類別在當時確實解決了一些效能瓶頸,也一直是許多高效能函式庫的首選工具。不過,這些方法原本是為了 JDK 內部使用而設計,但因為沒有有效的存取限制機制,導致許多外部開發者也開始使用它。
安全性與效能的取捨
這些方法雖然便利,但存在著重大的安全風險,並帶來了嚴重的安全隱憂。由於它們能夠繞過 Java 的型別系統和記憶體安全機制,一旦有人使用不當,可能導致程式出現未定義行為,甚至造成記憶體洩漏和 JVM 崩潰。更令人擔心的是,許多開發者在使用這些方法時往往忽略了必要的安全檢查。
隨著 Java 平台的演進,我們已經有了更安全、更標準化的替代方案。從 JDK 9 開始推出的 VarHandle
API 到 JDK 22 推出的 Foreign Function & Memory API,都提供了同樣高效但更安全的記憶體操作機制,這使得我們沒有必要繼續保留這些不安全的方法。
JEP 498 概觀
當程式首次呼叫 sun.misc.Unsafe
中任何的記憶體存取方法時,於執行階段發出警告。這些不支援的方法在 JDK 23 中已被永久棄用,並已被標準 API 所取代。團隊建議函式庫開發者應儘速遷移至支援的替代方案,以便應用程式能順利過渡至新版的 JDK。
JEP 498 的主要目標包括:
- 提升 Java 平台的預設安全性,降低不安全操作造成的風險
- 讓 Java 生態系做好準備,推動轉向使用標準化的 API
- 為最終移除這些不安全的方法做好準備,幫助開發人員意識到他們的應用程式是否直接或間接依賴於它們
- 降低應用程式因使用不安全方法而造成的風險
優點
- 提高 Java 平台的完整性和安全性:
sun.misc.Unsafe
中的記憶體存取方法已被棄用,並可能導致安全漏洞和程式崩潰。發出使用警告將鼓勵開發人員改用更安全且官方支援的 API - 促使舊版本的程式往更新的 JDK 版本遷移:透過發出對
sun.misc.Unsafe
的使用警告,開發人員將被迫遷移到更新的 JDK 版本中使用替代 API,從而確保他們的應用程式保持強健並且可以與未來版本相容 - 長期來看,可以簡化 JDK 的維護工作,並為 JVM 開發者提供更大的優化空間
缺點
- 許多現有的程式庫和應用程式都依賴於
sun.misc.Unsafe
中的記憶體存取方法,這些程式碼需要進行重大的修改後才能與未來的 JDK 版本相容 - 從
sun.misc.Unsafe
遷移到替代 API 需要時間和精力,特別是對於大型和複雜的函式庫,甚至某些特定功能的程式可能需要重新設計 - 某些極端情況下可能會輕微影響效能
介紹
淘汰計劃的實施策略將會分階段在各別的 JDK 功能版本中,進行棄用與移除 sun.misc.Unsafe
中的記憶體存取方法:
- JDK 23:將所有記憶體存取方法標記為待移除的已棄用狀態。在編譯階段使用這些方法的程式碼會產生棄用警告,提醒函式庫開發者這些方法即將被移除
- JDK 24:預設會在執行時期中第一次使用任何記憶體存取方法時(無論是直接使用還是通過反射)發出一則使用警告。無論使用了多少個方法或呼叫多少次,最多都只會發出一次警告,提醒這些方法即將被移除,並提示開發人員與使用者需要升級函式庫。以下是一個警告的範例:
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::setMemory has been called by com.foo.bar.Server (file:/tmp/foobarserver/thing.jar)
WARNING: Please consider reporting this to the maintainers of com.foo.bar.Server
WARNING: sun.misc.Unsafe::setMemory will be removed in a future release
- JDK 26 或之後版本:一旦使用記憶體存取方法(無論是直接使用還是通過反射)將會拋出例外,進一步提醒應用開發者與使用者這些方法的移除已迫在眉睫
- JDK 26 之後的版本:將移除那些自 JDK 9(2017 年)起就已經有標準替代方案的(堆上)記憶體存取方法
- 同樣地,之後也將會移除那些直到 JDK 22(2023 年)才有標準替代方案的(堆外和雙模)記憶體存取方法
所有記憶體存取方法及其對應的標準替代 API 可參考本篇。JDK 工作團隊可能會在適當的時候同時實施階段 4 和 5。
辨識 sun.misc.Unsafe
中記憶體存取方法的使用情況
我們通常不會在程式碼中直接使用 sun.misc.Unsafe
類別,不過有許多應用程式會直接或間接地依賴了使用這些記憶體存取方法的函式庫。
自 JDK 23 起,我們可以透過新的命令列選項 --sun-misc-unsafe-memory-access={allow|warn|debug|deny}
來評估程式會用到的函式庫是否受到這些已棄用方法的影響:
--sun-misc-unsafe-memory-access=allow
:允許使用記憶體存取方法,並且執行時期不會發出警告 → 此模式為 JDK 23 的預設行為--sun-misc-unsafe-memory-access=warn
:允許使用記憶體存取方法,但僅在第一次被使用時(無論是直接呼叫還是通過反射)發出單一警告。也就是說,無論使用哪種方法,或是被使用了多少次,最多都只會發出一個警告 → 此模式為 JDK 24 的預設行為。--sun-misc-unsafe-memory-access=debug
:允許使用記憶體存取方法,但在每次被使用時(無論是直接呼叫還是通過反射)都會發出一行警告和堆疊追蹤--sun-misc-unsafe-memory-access=deny
:禁止使用記憶體存取方法,並在每次使用時(無論是直接呼叫還是通過反射)拋出UnsupportedOperationException
。
不同階段的預設值
隨著各階段逐步進行,--sun-misc-unsafe-memory-access
選項的預設值也會根據所處的移除階段而變化調整:
- 階段一(JDK 23):預設值為
allow
,因此每次執行程式時都會包含--sun-misc-unsafe-memory-access=allow
- 階段二(JDK 24):預設值為
warn
,等同於執行 Java 時自動加上--sun-misc-unsafe-memory-access=warn
- 如果不希望產生警告的話,可以明確指定
--sun-misc-unsafe-memory-access=allow
- 如果不希望產生警告的話,可以明確指定
- 階段三(JDK 26 或更高版本):預設值為
deny
- 可以將其設為
warn
來避免拋出例外,但無法再使用allow
來完全關閉警告
- 可以將其設為
- 階段五:當所有記憶體存取方法都已被移除,這個選項將會被忽略,最終也會從 JDK 中移除
使用 JDK Flight Recorder 追蹤使用情況
我們可以透過 JDK Flight Recorder(JFR) 去偵測何時使用了記憶體存取方法。當我們在命令列上啟用 JFR 之後,JVM 就會在每次呼叫棄用的方法時紀錄新的 jdk.DeprecatedInvocation
事件,藉此找出是否有使用到 sun.misc.Unsafe
的記憶體存取方法。
例如,以下是建立 JFR 錄製檔並顯示 jdk.DeprecatedInvocation
事件的方式:
$ java -XX:StartFlightRecording:filename=recording.jfr ...
$ jfr print --events jdk.DeprecatedInvocation recording.jfr
jdk.DeprecatedInvocation {
startTime = 11:53:00.196 (2024-11-08)
method = sun.misc.Unsafe.staticFieldOffset(Field)
invocationTime = 11:53:00.174 (2024-11-08)
forRemoval = true
stackTrace = [
Foo.main(String[]) line: 16
...
]
}
$
總結
JEP 498 代表了 Java 平台在持續追求安全性和穩定性方面的重要里程碑。雖然這項變革在短期內可能會對某些現有的程式碼造成影響與不便,但從長遠來看,它將為整個 Java 生態系統帶來更穩定、更安全的發展環境。
對於開發者而言,現在是開始評估和規劃程式碼遷移的最佳時機。建議大家及早開始評估自己的程式碼是否有受到影響,並積極規劃轉移至標準 API 的時程,以確保在棄用方法正式移除時,應用程式能夠平順地過渡:
- 使用 JDK Flight Recorder 追蹤專案中是否使用了這些即將棄用的方法
- 評估使用
VarHandle
API 或 Foreign Function & Memory API 取代現有的不安全操作 - 在開發新功能時直接採用標準 API,避免使用
sun.misc.Unsafe
本篇文章的內容為老喬原創、二創或翻譯而來。雖已善盡校對、順稿與查核義務,但人非聖賢,多少仍會有疏漏之處難以避免。如果大家有任何問題、建議或指教,都歡迎在底下留言與老喬討論!