抖音「極客」適配 Android 5 ~ 9 等舊機型技術解讀,都是騷操作

想不到 2026 年,**像抖音這種超級 App 居然還可以適配 Android 5,還能做到流暢運行,這在技術上確實很值得膜拜**,因為舊機型的運行環境有多艱辛大家應該也知道:ART 虛擬機的 Heap 只有 256 MB、單個進程 fd 上限 1024、執行緒和進程數不超過 500 等,而為了讓抖音可以在老舊手機上流暢運行,技術上不得不做各種「騷操作」:

圖片

一、ART Heap 擴容

在 Android 5 - 7 上,即使你宣告了 largeHeap,一些舊機型的 ART 虛擬機 Heap 還是會被廠商限制在 256 MB,而抖音表示:「你說沒有就沒有?」

所以抖音透過 inline hook 控制 Copy GC 時序,再利用 main/backup 雙 malloc space 機制,動態釋放並建立更大空間,實現 Heap 從 256 MB 擴容到 740 - 980 MB,使得 Java OOM 發生率降低 60.77%。

怎麼理解?要知道 ART 的 Heap 是系統強控的,只能在 GC 時動手腳,比如 Copy GC 會在特定時機搬移內存,類似於趁著 GC「搬家」時插一腳(inline hook),然後釋放舊的 malloc space,重新申請一個更大的,再把資料搬過去,當然雙空間(main / backup)管理和資料搬運的時機都要卡好。

這就像搬家時,趁搬家公司在打包,偷偷把「小房子契約」換成「大房子」,而這個過程還需要不被發現異常。

二、ART Heap 擴容(Android 8 - 9)

Android 8 - 9 採用的是 region space,也就是「分區(region)堆」的管理方式,抖音針對 Android 8 - 9 只有 512 MB 的 region space 進行動態擴容,透過在低 4 GB 位址空間搜尋連續記憶體、修改 regions 陣列及相關結構,實現 Heap 擴至約 740 MB(+45%),讓整體當機率下降 8.8%,OOM 發生率下降 6.93%,GC 後記憶體滲透率超過 90% 的情況減少 73.34%。

因為 Android 8+ 用的是 region(分區堆),不能像 malloc space 那樣整體替換,而 region 陣列 + 位址空間是「固定結構」,所以可以在低 4 GB 位址空間找連續大塊記憶體,擴大 region 陣列(相當於增加「格子數量」),然後修改 ART 內部結構,讓 GC 認為這些是合法的 region。

大概就類似,你不給我申請記憶體,我就自己申請,然後讓 ART 相信這些記憶體是它自己管理的

三、FD / FD_SET 擴容

在 Android 9 等較舊版本上,一個進程裡的 fd 最大數量預設被限制在 1024,這對於抖音遠遠不夠,你可以把 fd 理解成 app 跟系統拿到的一張張「資源號碼牌」:

  • 開啟檔案要一個 fd
  • socket 網路連線要一個 fd
  • 某些管道、事件、匿名共享資源也要 fd

舊系統裡一個進程最多大概就 1024 張號碼牌,而抖音這種超級 App,圖片、影片、網路、快取、日誌、執行緒協作全都在搶這些牌。

所以抖音透過對所有的 fd_set 在堆內存上開闢 peer 物件,然後一一映射,例如:

  • 進入方法時觸發 fd_set 擴容,期間所有 fd_set 都會在堆上建立擴容後的 fd_set 映射
  • 離開方法時停止 fd_set 擴容,釋放當前擴容場景建立的 fd_set

透過這個騷操作,Android 9 以下版本 FD / FD_SET 超限問題幾乎全部解決,Android 9 以下系統整體當機率下降 7.23%。

也就是系統給 app 的「句柄名額」不夠了,抖音直接把這個上限頂開,你管不了的,我幫你管。

四、M:N 使用者態執行緒

因為 Android 9 和部分廠商的 Android 8 內核會限制 App 進程 + 執行緒總數必須 ≤ 500,所以:

  • 不能真的建立太多執行緒,因為內核不允許
  • FD_SET 也是固定大小,有 select 限制

所以抖音仍然透過 hook,透過攔截 clone 系統呼叫來透明代理 pthread 的建立,利用即時訊號定時器實現搶佔式排程,完整保存/恢復 CPU 上下文(含 TLS 隔離),讓單一 LWP 可承載多個虛擬執行緒,然後建立 peer 映射並透明擴展,從而解決 FD / FD_SET 的相關問題,整體當機率下降 7.23%。

抖音為什麼需要那麼多執行緒這件事並不重要,但 Hook 後自己接管執行緒建立,而不是真的建立執行緒,這個做法就很 6,這等於是建立「虛擬執行緒」,再映射到少量真實執行緒(LWP),這簡直就是自訂版本的底層協程有沒有

單個 LWP 可承載多個虛擬執行緒,抖音實測 1 個 LWP 穩定運行 15 個 Java 執行緒 + 3 個 native pthread,這裡有什麼難度?那可多了,比如:

  • 搶佔排程:怎麼用即時訊號(timer)打斷執行緒
  • 上下文切換:如何保存 CPU 暫存器 + 堆疊
  • TLS 隔離:每個虛擬執行緒都要有自己的 ThreadLocal

這就類似把 Java Thread 變成 Kotlin 協程的操作,把「系統資源限制問題」,全部變成「使用者態可控問題」。

最後

是不是看不懂?看不懂就對了,滿滿黑科技和騷操作有沒有?比如「即時的訊號 timer 搶佔式排程,完整 CPU 上下文切換(含 TLS 隔離)1 個 LWP 運行 15 個 Java 執行緒 + 3 個 native pthread」,這完全是場景不支援,我就創造場景,這個流程都是在系統漏洞上實現:

「你給的不夠,我需要更多,你給不了的,我自己拿,你管不了的,我幫你管」。

不過話說回來,現在很多低端機型,因為權限和安全都沒那麼嚴格,普遍是灰色產業的愛用對象,所以也不知道在這些普惠的使用者裡,有多少是真實使用者、多少是灰產工會,但這樣的相容與優化能力,的確令人佩服,值得膜拜。

原文可見:抖音:讓老手機刷抖音也流暢:我們做對了這三件事


原文出處:https://juejin.cn/post/7625239770092797995


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝9   💬11   ❤️3
564
🥈
我愛JS
📝2   💬7   ❤️2
149
🥉
💬1  
4
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登