0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Java运行包精简探索

jf_ro2CN3Fa 来源:jianshu 2023-01-31 16:42 次阅读

背景如下:

> 最近由于某些原因,需要做一个自带运行环境的程序。由于各种原因,选定了 JavaPython 作为备选语言。但是 Java 由于 JRE 的臃肿(100M+)以及 Spring Boot 的日渐臃肿(helloworld 15M),需要在这两方面进行 size 的缩减。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

环境

系统 :Ubuntu 16.04

GraalVM :GraalVM Community 21.3.0 (Based on OpenJDK 11.0.13)

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

环境准备

关键配置

环境变量设置

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

安装 native-image

guinstallnative-image

安装 native-image 需要的组件

apt-getinstallbuild-essentiallibz-devzlib1g-dev

注意 :这里有个其他文章都没有提到的坑。ld 需要更新到 2.26+,不然在构建过程中会报告莫名其妙的异常(这里我耗了大半天)。

apt-getinstallbinutils-2.26

另一个坑

最开始我是在一台 aliyun 的机器上实验的,没有注意到内存问题,在实验过程中遇到异常中断。排查 syslog 发现是 OOMKiller。排查发现我的可用内存只有 4G

后改为在本机机器 VMware 里的 Ubuntu 操作,内存分配到 8G。经观察,native-image 打包过程中会用到 5.2G 左右的内存,所以这里要注意一下。

至此,环境准备完成。

打包

helloworld 的尝试就跳过了,网上一搜一堆。先了解一下打包命令。

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 环境。

首先,需要使用 GraalVM 提供的配置工具,对想要打包的程序的一些静态分析无法分析到的信息进行采集。

如果你确认项目没有使用任何反射、代理等特性,可以省略这一步。执行

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

执行后,最好能跑一跑 testcase,尽量保证代码覆盖率 100%,避免打包后遇到 classnotfound。

执行完成后,终止执行。

如果需要多次运行采集信息 可以使用如下命令再次执行,工具会自动合并采集结果而不是覆盖。

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

执行结束,config 下生成如下 5 个文件:

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

坑又来了

根据网上的 说法,可以通过指定 -H:ConfigurationFileDirectories=./config 的方式来使用前面生成的配置文件。

但是,最后老是会把 -H:ConfigurationFileDirectories= 认为是指定的生成文件名,然而根据文档,-H:Name=xxx 才是指定输出文件名的参数

注意 :这个问题是开始在 CentOS 上操作遇到的,最后我在 Ubuntu 上又尝试用这种方式指定配置文件的时候,它生效了,原因未知。

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

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

打包命令:

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

这个命令也是经过反复多次尝试最终得出的可用命令,尝试的过程就略去3W字了。

注意 :如果你的应用需要对外提供 HTTP 服务,必须加上配置 --enable-http。如果提供 HTTPS 服务,则必须加 --enable-https。否则哪怕运行起来了访问也永远是 500。打包成功,目录下生成 solondem 文件。

执行运行:运行成功了,但是插件没加载。翻阅 solon 源码,发现插件加载的流程大致如下:

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

通过反复添加日志排查,发现:

在 graalvm native-image 下运行,这里扫描到 META-INF/solon 这个目录的 type,不是 file/jar,而是 resource。

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

所以到了这里自然就无法遍历目录下的文件了。于是我尝试让 resource 类型也走 file 的方式去扫描。

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

调研搜索后发现,GraalVM 内部资源管理自己实现了一套 FileSystem,URL 描述符定义为 resource,有一套自定义的 API(由于时间有限,暂未深入研究)。

对本来是目录类型的 resource 使用 File 方式去处理,得到的结果是 file not exists!但是对于确定的文件,是可以正常读取的。

于是我考虑预处理,在 GraalVM 外面就先把能扫描到的文件清单提取出来,通过配置的方式,插件扫描的时候直接返回预置的文件清单。

因为本地执行是可以正常扫描的,所以我在扫描结束的时候,增加一个输出:

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万字。

至此,主框架已经可以 run 起来了,但是嵌入式数据库 h2 还在作妖。

但是我尝试按照他们说的 使用 1.4.199 版本,却仍然各种异常。没办法,下载 h2 源码 加 log 排查吧。

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

首先,这里报空指针,那么唯一的可能就是 defaultProvider 为空。分析 defaultProvider 初始化过程:

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

发现了 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

访问接口、增删改查、静态页面 、日志都 OK 了。

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

幸福来得如此突然。

总结

最终可用的打包命令

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 有自己的文件系统实现,暂未找到遍历目录的方法;

第三方包不能运行时,大概率是由于反射没有检查到导致的 class not found;

排查第三方包问题时,一定要注意被吃掉的 Exception;

native-image 打包时需要 5G+ 内存。

补充

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 运行起来后,执行对应测试用例后,会自动将反射类信息生成到。

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 有自己的文件系统实现

暂未找到遍历目录的方法(即上文说说的).

如果你的程序中有涉及 ClassLoad.getResource("com.org") 这样的代码并打算对齐返回的结果以 File 或 jar 文件的方式扫描 com.org 下的所有类文件时会报错。

解决方式如上文所说,手动配置需要扫描的类文件,然后读取该配置(替代 getResource 方式)。

4. 控制 native 化后的二进制程序内存大小(配置参数不多说,一看就明白)

样例:

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






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • URL
    URL
    +关注

    关注

    0

    文章

    134

    浏览量

    14864
  • JAVA语言
    +关注

    关注

    0

    文章

    138

    浏览量

    19948
  • HTTP协议
    +关注

    关注

    0

    文章

    54

    浏览量

    9640
  • Ubuntu系统
    +关注

    关注

    0

    文章

    84

    浏览量

    3796
  • openjdk
    +关注

    关注

    0

    文章

    8

    浏览量

    2276

原文标题:Java 运行包精简探索(GraalVM)

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    java框架学习-Webwork2开发

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

    [原创]java从代码到运行的过程

    java从代码到运行的过程 用一个图来描述这个过程会比较容易理解:(链接地址:http://hiphotos.baidu.com/javass/pic/item
    发表于 10-31 11:44

    linux上的Java项目运行

    有一次我发现,没有服务器的root 和数据库的root,用sqlplus查询的乱码问题实在是太难解决了,所以就采用了java的jdbc 的方法进行查询,但是我发现在linux上面运行java 项目带 jar包的那种真的有点麻烦,
    发表于 07-24 06:58

    nodejs与java的互调用方法

    nodejs 与java的互调用方法很多,我们可选的是使用oracle 新的vm 引擎(graalvm很不错) 还有就是基于browserify进行包装,同时给java 提供一套require
    发表于 11-04 07:31

    如何在嵌入式设备上运行高性能Java

    如何在嵌入式设备上运行高性能Java
    发表于 03-28 09:43 16次下载

    Java 运行环境的安装、配置与运行

    Java 运行环境的安装、配置与运行 一、实验目的     1. 掌握下载 Java SDK 软件包。    2. 掌握设
    发表于 09-23 18:56 1.1w次阅读

    java参考大全

    Java语言、库及应用程序进行包括语法在内的详细介绍。我们将介绍Java语言产生的背景、发展过程,以及使它变得如此重要的原因。
    发表于 12-15 17:27 0次下载
    <b class='flag-5'>java</b>参考大全

    Java程序编译和运行的过程

    Java 虚拟机(JVM)是可运行Java 代码的假想计算机。只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上
    发表于 03-09 15:32 8722次阅读

    Java程序是如何运行

      JVM是Java运行时虚拟机,所有的Java程序都是在JVM沙箱中运行,每个Java程序就是一个独立的JVM进程。 谈到
    的头像 发表于 12-27 09:31 1639次阅读

    初学者:讲解Java程序的开发与运行原理

    可能刚刚接触编程的初学者会发现,编写一个Java程序其实很简单,但是Java程序的运行过程却是非常复杂的。关于Java程序工作原理这部分知识,虽然不要求编程学习者完全掌握,你但是至少需
    的头像 发表于 08-13 15:01 3194次阅读
    初学者:讲解<b class='flag-5'>Java</b>程序的开发与<b class='flag-5'>运行</b>原理

    CA850 Ver.3.20 C编译器运行包

    CA850 Ver.3.20 C编译器运行包
    发表于 05-04 19:03 0次下载
    CA850 Ver.3.20 C编译器<b class='flag-5'>运行包</b>

    eclipse怎么运行java项目

    在Eclipse中运行Java项目是非常简单的。下面了解一下如何在Eclipse中运行Java项目。 首先,确保您已经在Eclipse中创建了Jav
    的头像 发表于 12-06 11:25 1084次阅读

    eclipse设置java运行环境

    在Eclipse中设置Java运行环境是非常重要的,它能够确保你的代码能够正确地编译和运行。下面介绍如何设置Java运行环境。 下载和安装J
    的头像 发表于 12-06 11:29 637次阅读

    idea的java运行配置怎么弄

    Java是一种跨平台的编程语言,可以通过Java虚拟机(JVM)在不同的操作系统和硬件上运行。在运行Java程序之前,需要进行一些配置。本文
    的头像 发表于 12-06 14:04 681次阅读

    java环境配置成功后怎么运行

    Java环境配置成功后,我们可以使用几种方式来运行Java程序。下面将详细介绍这几种方式以及其使用方法。 命令行运行方式 在成功配置Java
    的头像 发表于 12-06 15:57 587次阅读