Java 27 改了三個預設值,有人在 1 核小機器上一個個驗證:先看懂數據,再照顧一下盯著終端機的你
從掘金一篇 1 核小機器實測 Java 27 三個預設值變化的文章出發,回顧物件頭縮減等改動對開發者的實際意義,順便照顧一下趕著驗證的你。
2026 年 9 月 23 日,掘金作者 Flynt 發布了一篇實測文章,回顧 9 月 15 日正式發布(GA)的 Java 27。Java 27 是非 LTS 版本,多數團隊的第一反應大概是「生產環境還錨在 25,27 與我無關」。這個判斷大體沒錯,但作者點出一個容易被忽略的角度:不升級不代表沒變化,Java 27 有三個改動是以「預設值」的形式定調的,分別是物件頭預設壓縮、TLS 1.3 預設帶上後量子演算法、所有環境預設改用 G1。作者手上正好有一臺 1 核心、不到 2G 記憶體的 Linux 小機器。這個配置有個特殊意義:從 JDK 9 開始,JVM 的 GC 選擇規則是單核或記憶體小於 1792MB 就退回 Serial,因此這臺機器剛好能直接看出三個預設值變化前後的差別。
最「物理」的改動:空物件從 16 位元組變 8 位元組
三個改動裡,緊湊物件頭(JEP 534)是最具體 measurable 的一個。它是 Project Lilliput 落地的最後一步:JDK 24 是實驗特性,JDK 25 轉正,JDK 27 預設開啟。作者用 JOL(Java Object Layout)印出空物件的記憶體佈局。在 JDK 25 上,一個空 Object 佔 16 位元組:8 位元組的 mark word、4 位元組的壓縮類指標,加上 4 位元組對齊湊整。同樣的程式放到 27 上,類指標被塞進 mark word 裡, instance size 直接降到 8 位元組,對齊損失歸零。
單一物件變小還只是表面,實際收益要看量。作者設計了一個樸素的實驗:固定 640MB 堆,往 List 裡不斷塞 new Object() 直到 OOM,兩版都明確指定 Serial GC,只留物件頭這一個變數。結果兩邊都在同一個位置倒下,塞進去的物件數一個不差:31,151,587 個。差別在實際佔用。JDK 25 每物件 20 位元組(含引用與對齊),實佔約 624MB;JDK 27 每物件 12 位元組,實佔約 375MB,節省約 40%。作者也解釋了為什麼 375MB 就會 OOM:觸發點並非堆被物件塞滿,而是 List 底層引用陣列擴容時,需要一次劃出近 300MB 的連續空間。所以這組數據比較的是「同樣多的物件誰佔得少」。對快取類服務、大集合、物件池這種堆裡充滿小物件的場景,這個收益最實在。
一個連帶的小坑
緊湊物件頭普及後,UseCompressedClassPointers 這個老參數在 JDK 25 被標記棄用,到了 27 正式廢棄。作者提醒,如果你的啟動腳本裡還寫著它,升級時會直接被拒。他是翻 release notes 才注意到這一點的。這類「不改程式碼但改環境」的坑,往往比新功能本身更容易咬人。
看懂版本更新的你,也值得被照顧一下
追版本、跑實測、翻 release notes,這些事看起來純粹是技術活,但背後常常是連續好幾個小時盯著終端機輸出與文件比對。如果你也正在做類似的驗證,眼睛、肩頸和作息其實都悄悄搭上了這段工時。之前我們聊過 llama.cpp 0.5.0 發布後熬夜編譯的你,情境很像:版本的變化可以用數據說清楚,但坐在螢幕前的你,也需要給自己設個停損點。看一輪輸出、起來倒杯水、讓眼睛離開文字幾分鐘,這些小事不會讓實驗變快,但會讓你撐得更穩。
回到這篇文章本身。它做對了一件很多版本解讀沒做到的事:不只在講「改了什麼」,而是用一臺規格極端的小機器,把抽象的預設值變化變成可核對的數字。至於 TLS 1.3 的後量子演算法與全系 G1 這兩個預設值,來源正文在物件頭之後便中斷,細節無法核實,這裡就不替作者補全了。有興趣的讀者可以自行回到原文對照驗證方法。