0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Java運行包精簡探索

jf_ro2CN3Fa ? 來源:jianshu ? 2023-01-31 16:42 ? 次閱讀

背景如下:

> 最近由于某些原因,需要做一個自帶運行環(huán)境的程序。由于各種原因,選定了 JavaPython 作為備選語言。但是 Java 由于 JRE 的臃腫(100M+)以及 Spring Boot 的日漸臃腫(helloworld 15M),需要在這兩方面進行 size 的縮減。

基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(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 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

環(huán)境準備

關(guān)鍵配置

環(huán)境變量設置

exportJAVA_HOME=/path/to/
exportPATH=/path/to//bin:$PATH

安裝 native-image

guinstallnative-image

安裝 native-image 需要的組件

apt-getinstallbuild-essentiallibz-devzlib1g-dev

注意 :這里有個其他文章都沒有提到的坑。ld 需要更新到 2.26+,不然在構(gòu)建過程中會報告莫名其妙的異常(這里我耗了大半天)。

apt-getinstallbinutils-2.26

另一個坑

最開始我是在一臺 aliyun 的機器上實驗的,沒有注意到內(nèi)存問題,在實驗過程中遇到異常中斷。排查 syslog 發(fā)現(xiàn)是 OOMKiller。排查發(fā)現(xiàn)我的可用內(nèi)存只有 4G。

后改為在本機機器 VMware 里的 Ubuntu 操作,內(nèi)存分配到 8G。經(jīng)觀察,native-image 打包過程中會用到 5.2G 左右的內(nèi)存,所以這里要注意一下。

至此,環(huán)境準備完成。

打包

helloworld 的嘗試就跳過了,網(wǎng)上一搜一堆。先了解一下打包命令。

2fc54412-8bee-11ed-bfe3-dac502259ad0.png

項目依賴

3003537e-8bee-11ed-bfe3-dac502259ad0.png

主要用到了 solon、solon-api、h2、weed3、logback、slf4j、jlhttp 等包。

首先,通過 Maven 把我的項目 solondemo 打包為可以運行的 jar,確保通過。

java-jarsolondemo.jar

可以正常運行并訪問。然后把 solondemo.jar 上傳到前面準備好的 GraalVM 環(huán)境。

首先,需要使用 GraalVM 提供的配置工具,對想要打包的程序的一些靜態(tài)分析無法分析到的信息進行采集。

如果你確認項目沒有使用任何反射、代理等特性,可以省略這一步。執(zhí)行

java-agentlib:native-image-agent=config-output-dir=./config/-jarsolondemo.jar

執(zhí)行后,最好能跑一跑 testcase,盡量保證代碼覆蓋率 100%,避免打包后遇到 classnotfound。

執(zhí)行完成后,終止執(zhí)行。

如果需要多次運行采集信息 可以使用如下命令再次執(zhí)行,工具會自動合并采集結(jié)果而不是覆蓋。

java-agentlib:native-image-agent=config-merge-dir=./config/-jarsolondemo.jar

執(zhí)行結(jié)束,config 下生成如下 5 個文件:

jni-config.json
predefined-classes-config.json
proxy-config.json
reflect-config.json
resource-config.json
serialization-config.json

坑又來了

根據(jù)網(wǎng)上的 說法,可以通過指定 -H:ConfigurationFileDirectories=./config 的方式來使用前面生成的配置文件。

但是,最后老是會把 -H:ConfigurationFileDirectories= 認為是指定的生成文件名,然而根據(jù)文檔,-H:Name=xxx 才是指定輸出文件名的參數(shù)

注意 :這個問題是開始在 CentOS 上操作遇到的,最后我在 Ubuntu 上又嘗試用這種方式指定配置文件的時候,它生效了,原因未知。

我采用了另外一種配置方式。把這些文件打包到 jar 包的 META-INF/native-image 目錄下。

3016a712-8bee-11ed-bfe3-dac502259ad0.png

打包命令:

native-image-jarsolondemo.jar--allow-incomplete-classpath-H:+ReportExceptionStackTraces--enable-http

這個命令也是經(jīng)過反復多次嘗試最終得出的可用命令,嘗試的過程就略去3W字了。

注意 :如果你的應用需要對外提供 HTTP 服務,必須加上配置 --enable-http。如果提供 HTTPS 服務,則必須加 --enable-https。否則哪怕運行起來了訪問也永遠是 500。打包成功,目錄下生成 solondem 文件。

執(zhí)行運行:運行成功了,但是插件沒加載。翻閱 solon 源碼,發(fā)現(xiàn)插件加載的流程大致如下:

30311aa2-8bee-11ed-bfe3-dac502259ad0.png

通過反復添加日志排查,發(fā)現(xiàn):

在 graalvm native-image 下運行,這里掃描到 META-INF/solon 這個目錄的 type,不是 file/jar,而是 resource。

304f28bc-8bee-11ed-bfe3-dac502259ad0.jpg

所以到了這里自然就無法遍歷目錄下的文件了。于是我嘗試讓 resource 類型也走 file 的方式去掃描。

3060731a-8bee-11ed-bfe3-dac502259ad0.png

調(diào)研搜索后發(fā)現(xiàn),GraalVM 內(nèi)部資源管理自己實現(xiàn)了一套 FileSystem,URL 描述符定義為 resource,有一套自定義的 API(由于時間有限,暫未深入研究)。

對本來是目錄類型的 resource 使用 File 方式去處理,得到的結(jié)果是 file not exists!但是對于確定的文件,是可以正常讀取的。

于是我考慮預處理,在 GraalVM 外面就先把能掃描到的文件清單提取出來,通過配置的方式,插件掃描的時候直接返回預置的文件清單。

因為本地執(zhí)行是可以正常掃描的,所以我在掃描結(jié)束的時候,增加一個輸出:

3077a008-8bee-11ed-bfe3-dac502259ad0.png

然后在配置中添加:

309a4e96-8bee-11ed-bfe3-dac502259ad0.jpg

scan 流程做如下修改

30a9125a-8bee-11ed-bfe3-dac502259ad0.png

插件掃描成功并運行。

30bdc75e-8bee-11ed-bfe3-dac502259ad0.png

掃描注解也有同樣的問題,排查過程與配置文件掃描類似,解決方案已與配置文件掃描的解決方案合并,略去 3萬字。

至此,主框架已經(jīng)可以 run 起來了,但是嵌入式數(shù)據(jù)庫 h2 還在作妖。

但是我嘗試按照他們說的 使用 1.4.199 版本,卻仍然各種異常。沒辦法,下載 h2 源碼 加 log 排查吧。

30e7278e-8bee-11ed-bfe3-dac502259ad0.png

首先,這里報空指針,那么唯一的可能就是 defaultProvider 為空。分析 defaultProvider 初始化過程:

310c32ae-8bee-11ed-bfe3-dac502259ad0.png

發(fā)現(xiàn)了 Class.forName,以及吃掉了異常:

e.printStackTrace();

打包,再來運行:

Causedby:java.lang.ClassNotFoundException
......
org.h2.store.fs.disk.FilePathDisk

好嘛。native-image 的 agent 居然沒有把這個掃出來。手動把這些添加到 reflect-config.json 里面,再打包運行。又報了個別的 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
}
]

成了!

312d7414-8bee-11ed-bfe3-dac502259ad0.png

訪問接口、增刪改查、靜態(tài)頁面 、日志都 OK 了。

3179dd68-8bee-11ed-bfe3-dac502259ad0.png

幸福來得如此突然。

總結(jié)

最終可用的打包命令

native-image-jarsolondemo.jar--allow-incomplete-classpath-H:+ReportExceptionStackTraces--enable-http

ld 需要升級到 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.

其他注意事項

-agentlib:native-image-agent 不一定能檢查出所有的反射;

GraalVM 有自己的文件系統(tǒng)實現(xiàn),暫未找到遍歷目錄的方法;

第三方包不能運行時,大概率是由于反射沒有檢查到導致的 class not found;

排查第三方包問題時,一定要注意被吃掉的 Exception;

native-image 打包時需要 5G+ 內(nèi)存。

補充

1. native-image后序列化失敗問題(比如 JSON.toJSONString(JavaBean))

fastjson1.2.68 版本下在程序啟動時增加如下代碼:

ParserConfig.getGlobalInstance().setAsmEnable(false);
SerializeConfig.getGlobalInstance().setAsmEnable(false);

2. 反射方法報錯

需將反射類手動配置到 reflect-config.json 文件中,也可在編譯打包成 jar 時添加配置

-agentlib:native-image-agent=config-output-dir=../META-INF/native-image

后打包,然后 java -jar 或 java -cp 運行起來后,執(zhí)行對應測試用例后,會自動將反射類信息生成到。

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)實現(xiàn)

暫未找到遍歷目錄的方法(即上文說說的).

如果你的程序中有涉及 ClassLoad.getResource("com.org") 這樣的代碼并打算對齊返回的結(jié)果以 File 或 jar 文件的方式掃描 com.org 下的所有類文件時會報錯。

解決方式如上文所說,手動配置需要掃描的類文件,然后讀取該配置(替代 getResource 方式)。

4. 控制 native 化后的二進制程序內(nèi)存大?。ㄅ渲脜?shù)不多說,一看就明白)

樣例:

./solondemo-Xmx16m-Xms16m-XX:MaxDirectMemorySize=8m






審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • URL
    URL
    +關(guān)注

    關(guān)注

    0

    文章

    139

    瀏覽量

    15369
  • JAVA語言
    +關(guān)注

    關(guān)注

    0

    文章

    138

    瀏覽量

    20112
  • HTTP協(xié)議
    +關(guān)注

    關(guān)注

    0

    文章

    66

    瀏覽量

    9744
  • Ubuntu系統(tǒng)
    +關(guān)注

    關(guān)注

    0

    文章

    91

    瀏覽量

    3991
  • openjdk
    +關(guān)注

    關(guān)注

    0

    文章

    8

    瀏覽量

    2321

原文標題:Java 運行包精簡探索(GraalVM)

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    java框架學習-Webwork2開發(fā)

    的wwexample中4:在WEB-INF中新建lib,把所有的jar包拷貝一份過去5:在Webwork-2.2.4發(fā)行包中找到taglib.tld,在/src/java/META-INF下面,拷貝到WEB-INF
    發(fā)表于 09-29 14:15

    [原創(chuàng)]java從代碼到運行的過程

    java從代碼到運行的過程 用一個圖來描述這個過程會比較容易理解:(鏈接地址:http://hiphotos.baidu.com/javass/pic/item
    發(fā)表于 10-31 11:44

    linux上的Java項目運行

    有一次我發(fā)現(xiàn),沒有服務器的root 和數(shù)據(jù)庫的root,用sqlplus查詢的亂碼問題實在是太難解決了,所以就采用了java的jdbc 的方法進行查詢,但是我發(fā)現(xiàn)在linux上面運行java 項目帶 jar包的那種真的有點麻煩,
    發(fā)表于 07-24 06:58

    nodejs與java的互調(diào)用方法

    nodejs 與java的互調(diào)用方法很多,我們可選的是使用oracle 新的vm 引擎(graalvm很不錯) 還有就是基于browserify進行包裝,同時給java 提供一套require
    發(fā)表于 11-04 07:31

    如何在嵌入式設備上運行高性能Java

    如何在嵌入式設備上運行高性能Java
    發(fā)表于 03-28 09:43 ?16次下載

    Java 運行環(huán)境的安裝、配置與運行

    Java 運行環(huán)境的安裝、配置與運行 一、實驗目的     1. 掌握下載 Java SDK 軟件包。    2. 掌握設
    發(fā)表于 09-23 18:56 ?1.1w次閱讀

    java參考大全

    Java語言、庫及應用程序進行包括語法在內(nèi)的詳細介紹。我們將介紹Java語言產(chǎn)生的背景、發(fā)展過程,以及使它變得如此重要的原因。
    發(fā)表于 12-15 17:27 ?0次下載
    <b class='flag-5'>java</b>參考大全

    Java程序編譯和運行的過程

    Java 虛擬機(JVM)是可運行Java 代碼的假想計算機。只要根據(jù)JVM規(guī)格描述將解釋器移植到特定的計算機上,就能保證經(jīng)過編譯的任何Java代碼能夠在該系統(tǒng)上
    發(fā)表于 03-09 15:32 ?8879次閱讀

    Java程序是如何運行

    ? JVM是Java運行時虛擬機,所有的Java程序都是在JVM沙箱中運行,每個Java程序就是一個獨立的JVM進程。 談到
    的頭像 發(fā)表于 12-27 09:31 ?1826次閱讀

    初學者:講解Java程序的開發(fā)與運行原理

    可能剛剛接觸編程的初學者會發(fā)現(xiàn),編寫一個Java程序其實很簡單,但是Java程序的運行過程卻是非常復雜的。關(guān)于Java程序工作原理這部分知識,雖然不要求編程學習者完全掌握,你但是至少需
    的頭像 發(fā)表于 08-13 15:01 ?3393次閱讀
    初學者:講解<b class='flag-5'>Java</b>程序的開發(fā)與<b class='flag-5'>運行</b>原理

    CA850 Ver.3.20 C編譯器運行包

    CA850 Ver.3.20 C編譯器運行包
    發(fā)表于 05-04 19:03 ?0次下載
    CA850 Ver.3.20 C編譯器<b class='flag-5'>運行包</b>

    eclipse怎么運行java項目

    在Eclipse中運行Java項目是非常簡單的。下面了解一下如何在Eclipse中運行Java項目。 首先,確保您已經(jīng)在Eclipse中創(chuàng)建了Jav
    的頭像 發(fā)表于 12-06 11:25 ?2047次閱讀

    eclipse設置java運行環(huán)境

    在Eclipse中設置Java運行環(huán)境是非常重要的,它能夠確保你的代碼能夠正確地編譯和運行。下面介紹如何設置Java運行環(huán)境。 下載和安裝J
    的頭像 發(fā)表于 12-06 11:29 ?1597次閱讀

    idea的java運行配置怎么弄

    Java是一種跨平臺的編程語言,可以通過Java虛擬機(JVM)在不同的操作系統(tǒng)和硬件上運行。在運行Java程序之前,需要進行一些配置。本文
    的頭像 發(fā)表于 12-06 14:04 ?2133次閱讀

    java環(huán)境配置成功后怎么運行

    Java環(huán)境配置成功后,我們可以使用幾種方式來運行Java程序。下面將詳細介紹這幾種方式以及其使用方法。 命令行運行方式 在成功配置Java
    的頭像 發(fā)表于 12-06 15:57 ?2238次閱讀