背景如下:
> 最近由于某些原因,需要做一個(gè)自帶運(yùn)行環(huán)境的程序。由于各種原因,選定了 Java 和 Python 作為備選語(yǔ)言。但是 Java 由于 JRE 的臃腫(100M+)以及 Spring Boot 的日漸臃腫(helloworld 15M),需要在這兩方面進(jìn)行 size 的縮減。
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
環(huán)境
系統(tǒng) :Ubuntu 16.04
GraalVM :GraalVM Community 21.3.0 (Based on OpenJDK 11.0.13)
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
環(huán)境準(zhǔn)備
關(guān)鍵配置
環(huán)境變量設(shè)置
exportJAVA_HOME=/path/to/exportPATH=/path/to/ /bin:$PATH
安裝 native-image
guinstallnative-image
安裝 native-image 需要的組件
apt-getinstallbuild-essentiallibz-devzlib1g-dev
注意 :這里有個(gè)其他文章都沒有提到的坑。ld 需要更新到 2.26+,不然在構(gòu)建過(guò)程中會(huì)報(bào)告莫名其妙的異常(這里我耗了大半天)。
apt-getinstallbinutils-2.26
另一個(gè)坑
最開始我是在一臺(tái) aliyun 的機(jī)器上實(shí)驗(yàn)的,沒有注意到內(nèi)存問(wèn)題,在實(shí)驗(yàn)過(guò)程中遇到異常中斷。排查 syslog 發(fā)現(xiàn)是 OOMKiller。排查發(fā)現(xiàn)我的可用內(nèi)存只有 4G。
后改為在本機(jī)機(jī)器 VMware 里的 Ubuntu 操作,內(nèi)存分配到 8G。經(jīng)觀察,native-image 打包過(guò)程中會(huì)用到 5.2G 左右的內(nèi)存,所以這里要注意一下。
至此,環(huán)境準(zhǔn)備完成。
打包
helloworld 的嘗試就跳過(guò)了,網(wǎng)上一搜一堆。先了解一下打包命令。
項(xiàng)目依賴
主要用到了 solon、solon-api、h2、weed3、logback、slf4j、jlhttp 等包。
首先,通過(guò) Maven 把我的項(xiàng)目 solondemo 打包為可以運(yùn)行的 jar,確保通過(guò)。
java-jarsolondemo.jar
可以正常運(yùn)行并訪問(wèn)。然后把 solondemo.jar 上傳到前面準(zhǔn)備好的 GraalVM 環(huán)境。
首先,需要使用 GraalVM 提供的配置工具,對(duì)想要打包的程序的一些靜態(tài)分析無(wú)法分析到的信息進(jìn)行采集。
如果你確認(rèn)項(xiàng)目沒有使用任何反射、代理等特性,可以省略這一步。執(zhí)行
java-agentlib:native-image-agent=config-output-dir=./config/-jarsolondemo.jar
執(zhí)行后,最好能跑一跑 testcase,盡量保證代碼覆蓋率 100%,避免打包后遇到 classnotfound。
執(zhí)行完成后,終止執(zhí)行。
如果需要多次運(yùn)行采集信息 可以使用如下命令再次執(zhí)行,工具會(huì)自動(dòng)合并采集結(jié)果而不是覆蓋。
java-agentlib:native-image-agent=config-merge-dir=./config/-jarsolondemo.jar
執(zhí)行結(jié)束,config 下生成如下 5 個(gè)文件:
jni-config.json predefined-classes-config.json proxy-config.json reflect-config.json resource-config.json serialization-config.json
坑又來(lái)了
根據(jù)網(wǎng)上的 說(shuō)法,可以通過(guò)指定 -H:ConfigurationFileDirectories=./config 的方式來(lái)使用前面生成的配置文件。
但是,最后老是會(huì)把 -H:ConfigurationFileDirectories= 認(rèn)為是指定的生成文件名,然而根據(jù)文檔,-H:Name=xxx 才是指定輸出文件名的參數(shù)。
注意 :這個(gè)問(wèn)題是開始在 CentOS 上操作遇到的,最后我在 Ubuntu 上又嘗試用這種方式指定配置文件的時(shí)候,它生效了,原因未知。
我采用了另外一種配置方式。把這些文件打包到 jar 包的 META-INF/native-image 目錄下。
打包命令:
native-image-jarsolondemo.jar--allow-incomplete-classpath-H:+ReportExceptionStackTraces--enable-http
這個(gè)命令也是經(jīng)過(guò)反復(fù)多次嘗試最終得出的可用命令,嘗試的過(guò)程就略去3W字了。
注意 :如果你的應(yīng)用需要對(duì)外提供 HTTP 服務(wù),必須加上配置 --enable-http。如果提供 HTTPS 服務(wù),則必須加 --enable-https。否則哪怕運(yùn)行起來(lái)了訪問(wèn)也永遠(yuǎn)是 500。打包成功,目錄下生成 solondem 文件。
執(zhí)行運(yùn)行:運(yùn)行成功了,但是插件沒加載。翻閱 solon 源碼,發(fā)現(xiàn)插件加載的流程大致如下:
通過(guò)反復(fù)添加日志排查,發(fā)現(xiàn):
在 graalvm native-image 下運(yùn)行,這里掃描到 META-INF/solon 這個(gè)目錄的 type,不是 file/jar,而是 resource。
所以到了這里自然就無(wú)法遍歷目錄下的文件了。于是我嘗試讓 resource 類型也走 file 的方式去掃描。
調(diào)研搜索后發(fā)現(xiàn),GraalVM 內(nèi)部資源管理自己實(shí)現(xiàn)了一套 FileSystem,URL 描述符定義為 resource,有一套自定義的 API(由于時(shí)間有限,暫未深入研究)。
對(duì)本來(lái)是目錄類型的 resource 使用 File 方式去處理,得到的結(jié)果是 file not exists!但是對(duì)于確定的文件,是可以正常讀取的。
于是我考慮預(yù)處理,在 GraalVM 外面就先把能掃描到的文件清單提取出來(lái),通過(guò)配置的方式,插件掃描的時(shí)候直接返回預(yù)置的文件清單。
因?yàn)楸镜貓?zhí)行是可以正常掃描的,所以我在掃描結(jié)束的時(shí)候,增加一個(gè)輸出:
然后在配置中添加:
scan 流程做如下修改
插件掃描成功并運(yùn)行。
掃描注解也有同樣的問(wèn)題,排查過(guò)程與配置文件掃描類似,解決方案已與配置文件掃描的解決方案合并,略去 3萬(wàn)字。
至此,主框架已經(jīng)可以 run 起來(lái)了,但是嵌入式數(shù)據(jù)庫(kù) h2 還在作妖。
但是我嘗試按照他們說(shuō)的 使用 1.4.199 版本,卻仍然各種異常。沒辦法,下載 h2 源碼 加 log 排查吧。
首先,這里報(bào)空指針,那么唯一的可能就是 defaultProvider 為空。分析 defaultProvider 初始化過(guò)程:
發(fā)現(xiàn)了 Class.forName,以及吃掉了異常:
e.printStackTrace();
打包,再來(lái)運(yùn)行:
Causedby:java.lang.ClassNotFoundException ...... org.h2.store.fs.disk.FilePathDisk
好嘛。native-image 的 agent 居然沒有把這個(gè)掃出來(lái)。手動(dòng)把這些添加到 reflect-config.json 里面,再打包運(yùn)行。又報(bào)了個(gè)別的 class not found。再添加,再打包。
[ ........, { "name":"org.h2.store.fs.FilePathDisk", "methods":[{"name":"","parameterTypes":[]}] }, { "name":"org.h2.store.fs.FilePathMem", "methods":[{"name":" ","parameterTypes":[]}] }, { "name":"org.h2.store.fs.FilePathMemLZF", "methods":[{"name":" ","parameterTypes":[]}] }, { "name":"org.h2.store.fs.FilePathNioMem", "methods":[{"name":" ","parameterTypes":[]}] }, { "name":"org.h2.store.fs.FilePathNioMemLZF", "methods":[{"name":" ","parameterTypes":[]}] }, { "name":"org.h2.store.fs.FilePathNioMapped", "methods":[{"name":" ","parameterTypes":[]}] }, { "name":"org.h2.store.fs.FilePathAsync", "methods":[{"name":" ","parameterTypes":[]}] }, { "name":"org.h2.store.fs.FilePathZip", "methods":[{"name":" ","parameterTypes":[]}] }, { "name":"org.h2.store.fs.FilePathRetryOnInterrupt", "methods":[{"name":" ","parameterTypes":[]}] }, { "name":"org.h2.store.fs.FilePathNio", "methods":[{"name":" ","parameterTypes":[]}] }, { "name":"org.h2.store.fs.FilePathSplit", "methods":[{"name":" ","parameterTypes":[]}] }, { "name":"org.h2.mvstore.db.MVTableEngine", "methods":[{"name":" ","parameterTypes":[]}], "allDeclaredFields":true } ]
成了!
訪問(wèn)接口、增刪改查、靜態(tài)頁(yè)面 、日志都 OK 了。
幸福來(lái)得如此突然。
總結(jié)
最終可用的打包命令
native-image-jarsolondemo.jar--allow-incomplete-classpath-H:+ReportExceptionStackTraces--enable-http
ld 需要升級(jí)到 2.26+
root@ubuntu:/home/hx/graalvm/demo3#ld-version GNUld(GNUBinutilsforUbuntu)2.26.1 Copyright(C)2015FreeSoftwareFoundation,Inc. Thisprogramisfreesoftware;youmayredistributeitunderthetermsof theGNUGeneralPublicLicenseversion3or(atyouroption)alaterversion. Thisprogramhasabsolutelynowarranty.
其他注意事項(xiàng)
-agentlib:native-image-agent 不一定能檢查出所有的反射;
GraalVM 有自己的文件系統(tǒng)實(shí)現(xiàn),暫未找到遍歷目錄的方法;
第三方包不能運(yùn)行時(shí),大概率是由于反射沒有檢查到導(dǎo)致的 class not found;
排查第三方包問(wèn)題時(shí),一定要注意被吃掉的 Exception;
native-image 打包時(shí)需要 5G+ 內(nèi)存。
補(bǔ)充
1. native-image后序列化失敗問(wèn)題(比如 JSON.toJSONString(JavaBean))
fastjson1.2.68 版本下在程序啟動(dòng)時(shí)增加如下代碼:
ParserConfig.getGlobalInstance().setAsmEnable(false); SerializeConfig.getGlobalInstance().setAsmEnable(false);
2. 反射方法報(bào)錯(cuò)
需將反射類手動(dòng)配置到 reflect-config.json 文件中,也可在編譯打包成 jar 時(shí)添加配置
-agentlib:native-image-agent=config-output-dir=../META-INF/native-image
后打包,然后 java -jar 或 java -cp 運(yùn)行起來(lái)后,執(zhí)行對(duì)應(yīng)測(cè)試用例后,會(huì)自動(dòng)將反射類信息生成到。
reflect-config.json 文件中(但真的不一定)。配置文件樣例:
[ { "name":"com.test.A", "allDeclaredFields":true, "allPublicFields":true, "queryAllPublicMethods":true, "methods":[ {"name":"getA","parameterTypes":[]}, {"name":"getD","parameterTypes":[]}, {"name":"getF","parameterTypes":[]}, {"name":"getI","parameterTypes":[]}, {"name":"getQ","parameterTypes":[]}, {"name":"getR","parameterTypes":[]}, {"name":"getT","parameterTypes":[]}, {"name":"getY","parameterTypes":[]}, {"name":"getU","parameterTypes":[]}, {"name":"getV","parameterTypes":[]} ] } ]
3. GraalVM 有自己的文件系統(tǒng)實(shí)現(xiàn)
暫未找到遍歷目錄的方法(即上文說(shuō)說(shuō)的).
如果你的程序中有涉及 ClassLoad.getResource("com.org") 這樣的代碼并打算對(duì)齊返回的結(jié)果以 File 或 jar 文件的方式掃描 com.org 下的所有類文件時(shí)會(huì)報(bào)錯(cuò)。
解決方式如上文所說(shuō),手動(dòng)配置需要掃描的類文件,然后讀取該配置(替代 getResource 方式)。
4. 控制 native 化后的二進(jìn)制程序內(nèi)存大小(配置參數(shù)不多說(shuō),一看就明白)
樣例:
./solondemo-Xmx16m-Xms16m-XX:MaxDirectMemorySize=8m
審核編輯:劉清
-
URL
+關(guān)注
關(guān)注
0文章
139瀏覽量
15312 -
JAVA語(yǔ)言
+關(guān)注
關(guān)注
0文章
138瀏覽量
20076 -
HTTP協(xié)議
+關(guān)注
關(guān)注
0文章
61瀏覽量
9705 -
Ubuntu系統(tǒng)
+關(guān)注
關(guān)注
0文章
85瀏覽量
3910 -
openjdk
+關(guān)注
關(guān)注
0文章
8瀏覽量
2309
原文標(biāo)題:Java 運(yùn)行包精簡(jiǎn)探索(GraalVM)
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論