国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

關于Android中自定義ClassLoader耗時問題的追查

2019-10-22 18:10:13
字體:
來源:轉載
供稿:網友

前言

Android中類加載器有BootClassLoader,URLClassLoader,
PathClassLoader,DexClassLoader,BaseDexClassLoader,等都最終繼承自java.lang.ClassLoader

最近在優化西瓜視頻客戶端冷啟動速度時,發現在關閉插件 ClassLoader 注入的情況下,啟動速度提升了300ms左右,但是西瓜在啟動階段并沒有使用到插件,那么這么大的耗時是怎么來的呢?下面話不多說了,來一起看看詳細的介紹吧。

猜原因

首先看下西瓜目前使用的插件 ClassLoader 是怎么注入的,大致代碼如下:

Android,ClassLoader,耗時

代碼大致意思是在 PathClassLoader 和 BootClassLoader 之間插入了一個 DelegateClassLoader,而在 DelegateClassLoader 的 findClass 方法中去執行插件 Class 的加載。

為了方便驗證,寫一個簡單的測試Demo,測試加載一個類的耗時:

Android,ClassLoader,耗時

以小米Max2,Android7.1.1機型為例,測試不注入和注入 DelegateClassLoader 加載一個類的耗時:

不注入:60μs

注入后:472μs

差不多慢了8倍,測試了幾款手機基本數據都差不多,但是4.x手機上這兩種情況下耗時差別卻很小。

DelegateClassLoader.findClass耗時?

因為雙親委托機制,所以宿主中所有類的加載都會走到 DelegateClassLoader.findClass 中,但是 DelegateClassLoader 中因為不存在宿主類,所以必然找不到,因此一個宿主類的加載會多調用了一次無用的 findClass 方法,一次findClass的調用會帶來如此大的耗時?于是將 DelegateClassLoader 代碼精簡成下面這樣的:

Android,ClassLoader,耗時

這樣,DelegateClassLoader 中沒有做任何插件類加載的邏輯,只是做了一個中轉到父 ClassLoader 的 loadClass 的操作。

結果依然是8倍左右的耗時差距。

java方法調用耗時?

上面方案里只是比不注入自定義 ClassLoader 多了一次 DelegateClassLoader.loadClass 方法的調用,理論上不可能存在這么大的耗時。如果說多調用一次 java 方法 DelegateClassLoader.loadClass 會有8倍的耗時差異的話,那么多調用兩次是不是就是16倍的差異?

于是嘗試注入兩個 DelegateClassLoader,類似這樣:

Android,ClassLoader,耗時

但是結果還是8倍左右的耗時差異,并非16倍,這么說不是方法調用帶來的性能損耗。

自定義ClassLoader耗時?

所以猜測可能是系統對 PathClassLoader 有什么優化?然后直接構造一個空的 PathClassLoader 注入到 PathClassLoader 和 BootClassLoader 中間,類似這樣:

Android,ClassLoader,耗時

神奇的8倍耗時差異沒了!所以真的是系統對 PathClassLoader 有優化?

帶著這個疑問我們來看下 ClassLoader 的源碼,以 Android 7.1.1 源碼為例。

ClassLoader#loadClass

首先來看下源頭,ClassLoader 的 loadClass 源碼,核心代碼如下:

Android,ClassLoader,耗時

大致流程是先調用 findLoadedClass 嘗試從已加載的 class 中查找,然后再調用父 ClassLoader 的 loadClass 查找,如果依然沒有找到的話,最后再調用自己的 findClass 加載。

在 JVM 中,類第一次加載時,肯定之前是沒有加載過的,因此 findLoadedClass 應該是返回 null 的,而 BootClassLoader 中只有系統類,因此宿主類的加載應該是調用了 PathClassLoader#findClass 加載的。

PathClassLoader#findClass

那么我們再來看看 PathClassLoader#findClass 的源碼,調用鏈大致如下:

Android,ClassLoader,耗時

如果說系統對 ClassLoader 有某些優化,那么應該只要重點關注在調用鏈中有用到 ClassLoader 的地方即可。

整個 findClass 流程中使用到 ClassLoader 的地方并不多,只有 ClassLinker::RegisterDexFile 和 ClassLinker::SetupClass 中使用到了。

  • ClassLinker::RegisterDexFile 中是對 ClassLoader 取 class_table 的簡單操作;
  • ClassLinker::SetupClass 中是給加載好的 class 設置 ClassLoader,兩個方法對 ClassLoader 的操作看上去是不存在任何優化的,理論上不會導致性能損耗,這里不再貼代碼。

如果不是 findClass 里有優化,難道在 ClassLoader#findLoadedClass 里?

ClassLoader#findLoadedClass

再來看看 ClassLoader#findLoadedClass 的源碼,調用鏈大致如下:

Android,ClassLoader,耗時

首先來看下c層調用的第一個方法 VMClassLoader_findLoadedClass :

Android,ClassLoader,耗時

這里主要有兩個分支,第一個分支,第12行調用 ClassLinker#LookupClass :

Android,ClassLoader,耗時

這里大致意思是從 ClassLoader 中找到 ClassTable ,然后調用 ClassTable#Lookup 而這個 ClassTable 里面就保存了已經加載過的類以及啟動時從 app image 中加載的類(app image的作用是記錄已經編譯好的“熱代碼”,并且在啟動時一次性把它們加載到緩存,參考Tinker博客)。如果一個類是首次加載且不在 app image 中,那么這里會返回 null。

這樣就會走到第二個分支(第25行) ClassLinker::FindClassInPathClassLoader 中

Android,ClassLoader,耗時

這里主要分為兩個部分:

  • 第一部分:從37行開始,反射從 Java 層的 PathClassLoader 取得 DexPathList,然后再反射從 DexPathList 中取得 dexElements,然后再遍歷 dexElements,從每個 Element 中取得 dexFile,然后再從 DexFile 中取得 mCookie,然后通過 mCookie 得到 c 層的 DexFile,最后調用 c 層 DexFile#FindClassDef 來真正的執行類的加載,整個流程其實就是在 c 層把 Java 層的 PathClassLoader#findClass 邏輯走了一遍;
  • 第二部分:采用遞歸的方式,從 BootClassLoader 開始依次到 PathClassLoader 逐個調用 FindClassInPathClassLoader,直到找到 class 為止,相當于把 Java 層 ClassLoader 的雙親委托加載 class 的機制在 c 層做了一遍,這個其實是 ART 上對 class 加載做的一個優化,但是在 Dalvik 中是沒有這段邏輯的,可以參考/dalvik/native/javalangVMClassLoader.cpp。

重點來了!因為上面使用到了反射機制取 PathClassLoader 中的字段,為了保證這套機制不出問題,這里面加了個校驗:

Android,ClassLoader,耗時

如果 ClassLoader 鏈中存在不認識的 ClassLoader,也就是說 ClassLoader 的類不是 BootClassLoader 和 PathClassLoader,那么就認為加載類失敗。當然這里加載失敗的話,并不會影響最終類加載結果,因為在 Java 層 findLoadedClass 失敗后,會走到 findClass 中的。

結論

在 Android ART 中默認的 ClassLoader 機制,在 ClassLoader#findLoadedClass 時就把 JVM 中的 findLoadedClass 和 findClass 兩件事情都做了。但是如果在 class loader 鏈中存在自定義 ClassLoader,那么這個機制就會失效,會回退到 JVM 默認的 ClassLoader 機制。

回到上面的問題,由于我們自定義了 ClassLoader,導致 Art 的 ClassLoader 機制回退到了 JVM 的默認類加載機制,而 JVM 默認的類加載機制存在多次 JNI 調用,JNI 調用本身性能是比直接方法調用耗時高幾倍的,這里不再詳細展開,因此也就能解釋前面所說的幾倍的耗時差異了。

參考

Android N混合編譯與對熱補丁影響解析

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網的支持。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 扎赉特旗| 柞水县| 昔阳县| 汕尾市| 甘南县| 靖远县| 启东市| 神木县| 特克斯县| 富裕县| 诸暨市| 太白县| 天水市| 景洪市| 平邑县| 罗源县| 芷江| 肃北| 都匀市| 睢宁县| 城口县| 穆棱市| 秦皇岛市| 平湖市| 桃江县| 民乐县| 通州区| 哈巴河县| 北辰区| 武平县| 左权县| 广灵县| 弥渡县| 灌南县| 德令哈市| 文水县| SHOW| 金坛市| 房山区| 东至县| 龙岩市|