使用GraalVM Native Image将Java Swing应用Native化

简介: Java Swing背景        Java Swing是从1.6时代开始成熟的Java桌面应用GUI框架,也是以前大学里做算法毕设时走Java栈码农的必备开发能力之一。虽然现如今Java的桌面端能力已有多种更优秀的替代方案,例如JavaFX、Eclipse RCP等,但作为经典GUI框架,Swing以其简单的编码模式、优秀的跨平台能力、JRE默认自带包以及较小的JAR分发包,一直是我的Jav

Java Swing背景

        Java Swing是从1.6时代开始成熟的Java桌面应用GUI框架,也是以前大学里做算法毕设时走Java栈码农的必备开发能力之一。虽然现如今Java的桌面端能力已有多种更优秀的替代方案,例如JavaFX、Eclipse RCP等,但作为经典GUI框架,Swing以其简单的编码模式、优秀的跨平台能力、JRE默认自带包以及较小的JAR分发包,一直是我的Java桌面应用开发首选。

Java Swing缺点

        Swing作为桌面应用有个较大的缺陷,其实也是Java在普通软件使用者里遇到的问题就是需要安装JRE环境,这个对于基于C++、C#开发的桌面应用相比,增加了用户的使用门槛,但如果分发软件时带上了JRE后又会增加了太多体积,更甚者,Swing的运行效率跟原生应用相比慢上了好几倍。上述的问题也是Java栈码农写桌面应用历来的痛点问题。

Java AOT技术简介

        随着Java技术的发展,AOT技术如雨后春笋般涌现,从最早的Android ART替代Dalvik虚拟机,到GraalVM替代普通JDK,从原理上用一句话解释就是将动态语言的JIT过程通过AOT进行静态化。Java在Native这条道路上越走越宽,随之带来的好处就是开发者不需改变任何代码就可以让Java程序的运行性能媲美原生应用。

GraalVM特性简介

        GraalVM的发布将JVM的适用范围从Java扩展到了Scala、Kotlin、Groovy、Clojure、R、Python、JavaScript和Ruby,从本质上讲GraalVM允许开发人员在单个应用程序中以多种语言和库高效地运行代码。其中最令我感到兴奋的就是Native Image组件,它是一种将Java代码提前编译为独立可执行文件的技术,该执行文件里包含了应用程序类、依赖、运行时库以及JDK静态连接的本机代码。相比基于运行在普通JVM的Java程序,其具备更快的启动时间和更低的运行时开销,并且可以完全脱离JVM环境。

本文目标

        既然GraalVM Native Image这么强,那么Swing程序是否同样可以编译为独立可执行文件呢?我特意翻找了GraalVM的讨论帖,发现该问题早在2019年就有人提出过Issue(https://github.com/oracle/graal/issues/1327),但直到如今一直都没有Close,那么事实真的如此么?本着折腾心理,在空暇时我对Swing程序Native化做了不少的实验也踩了不少坑,终于在最近成功实现了目标。

        因此本文的目的很简单,分享利用GraalVM的Native Image功能将Swing程序在Windows系统下Native化的Step by Step过程,最终生成脱离JVM的exe可执行程序。虽然本文是以Swing程序的Native化作为教程分享目标,但实际上也同样适用于所有Java程序的Native化,希望可以具备参考意义。

环境准备

系统

Windows 7+ (64位)

GraalVM环境

GraalVM Community Edition 21.2.0 (Java8)

GraalVM加入到系统环境变量中

安装组件:

Native Image Installable SVM 21.2.0

安装组件命令:

gu install -L native-image-installable-svm-java8-windows-amd64-21.2.0.jar

下载地址:

https://github.com/graalvm/graalvm-ce-builds/releases/tag/vm-21.2.0

注意点:

Java8的GraalVM最高仅支持到了21.3.1,但该版本存在一些bug,因此只能选择次新的21.2.0版本。

Visual Studio环境

Visual Studio Community 2017+

安装组件:

Visual C++

Swing程序

        由于编译Native Image的特殊性,我们需要在Swing程序的main方法入口最开始处强制设置一个环境变量,否则在完成后面的编译后,运行会失败:

System.setProperty("java.home", ".");

        将Swing程序编译打包为jar文件,并且将依赖库打入Jar中,取名:test.jar。正常运行Swing程序的命令为:

java -jar test.jar

开始编译Native Image

进入VC编译环境

        开始菜单找到并打开 “Visual Studio 2017\Visual Studio Tools\VC\适用于 VS 2017 的 x64 本机工具命令提示”

进入Jar文件目录

        假设Jar文件目录名为native-test,目录里只有test.jar文件,我们通过cd命令进入到native-test目录内。

以代理类模式运行Jar文件采集meta信息

        此步骤的意义在于让Native Image在Swing应用运行过程中监控到所有运行时动态加载的类,包括jni加载类、代理类、反射类、静态资源文件等,这些类必须要应用正常运行时才能感知到,无法通过系统简单静态引用分析获取。通过以下命令可以将采集到的所有meta信息保存在native-image子目录下

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

        采集的步骤可以通过以下方式重复运行,以达到人为干预触达到应用所有分支逻辑的作用,config-merge-dir命令可以将所有采集到的meta信息进行去重合并

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

        以下是采集完成后生成的meta信息文件:

手工补充采集到的meta文件

        此步骤主要是因为Native Image对于Swing应用的运行时类监控存在缺陷,没有将必要的系统类加入到meta信息中,需要手工补充进去,否则在完成编译后运行文件时会报错找不到类。

native-image\jni-config.json 添加以下内容到json末尾节点:

{

"name": "java.lang.Object",

"methods": [

{

"name": "toString"

}

]

},

{

"name": "java.lang.Class",

"methods": [

{

"name": "getComponentType"

}

]

},

{

"name": "java.lang.String",

"methods": [

{

"name": "getBytes"

},

{

"name": "toCharArray"

},

{

"name": "<init>"

}

]

},

{

"name": "java.lang.reflect.Method",

"methods": [

{

"name": "getParameterTypes"

},

{

"name": "getReturnType"

}

]

},

{

"name": "java.nio.Buffer",

"methods": [

{

"name": "position"

}

]

},

{

"name": "java.nio.ByteBuffer",

"methods": [

{

"name": "array"

},

{

"name": "arrayOffset"

}

]

},

{

"name": "java.nio.CharBuffer",

"methods": [

{

"name": "array"

},

{

"name": "arrayOffset"

}

]

},

{

"name": "java.nio.ShortBuffer",

"methods": [

{

"name": "array"

},

{

"name": "arrayOffset"

}

]

},

{

"name": "java.nio.IntBuffer",

"methods": [

{

"name": "array"

},

{

"name": "arrayOffset"

}

]

},

{

"name": "java.nio.LongBuffer",

"methods": [

{

"name": "array"

},

{

"name": "arrayOffset"

}

]

},

{

"name": "java.nio.FloatBuffer",

"methods": [

{

"name": "array"

},

{

"name": "arrayOffset"

}

]

},

{

"name": "java.nio.DoubleBuffer",

"methods": [

{

"name": "array"

},

{

"name": "arrayOffset"

}

]

},

{

"name": "java.lang.Void",

"fields": [

{

"name": "TYPE"

}

]

},

{

"name": "java.lang.Boolean",

"methods": [

{

"name": "<init>"

}

],

"fields": [

{

"name": "value"

},

{

"name": "TYPE"

}

]

},

{

"name": "java.lang.Byte",

"methods": [

{

"name": "<init>"

}

],

"fields": [

{

"name": "value"

},

{

"name": "TYPE"

}

]

},

{

"name": "java.lang.Character",

"methods": [

{

"name": "<init>"

}

],

"fields": [

{

"name": "value"

},

{

"name": "TYPE"

}

]

},

{

"name": "java.lang.Short",

"methods": [

{

"name": "<init>"

}

],

"fields": [

{

"name": "value"

},

{

"name": "TYPE"

}

]

},

{

"name": "java.lang.Integer",

"methods": [

{

"name": "<init>"

}

],

"fields": [

{

"name": "value"

},

{

"name": "TYPE"

}

]

},

{

"name": "java.lang.Long",

"methods": [

{

"name": "<init>"

}

],

"fields": [

{

"name": "value"

},

{

"name": "TYPE"

}

]

},

{

"name": "java.lang.Float",

"methods": [

{

"name": "<init>"

}

],

"fields": [

{

"name": "value"

},

{

"name": "TYPE"

}

]

},

{

"name": "java.lang.Double",

"methods": [

{

"name": "<init>"

}

],

"fields": [

{

"name": "value"

},

{

"name": "TYPE"

}

]

},

{

"name": "sun.java2d.d3d.D3DRenderQueue$1",

"methods": [

{

"name": "run"

}

]

},

{

"name": "sun.java2d.d3d.D3DGraphicsDevice$1",

"methods": [

{

"name": "run"

}

]

},

{

"name": "sun.java2d.d3d.D3DSurfaceData$1",

"methods": [

{

"name": "run"

}

]

},

{

"name": "sun.java2d.d3d.D3DSurfaceData",

"fields": [

{

"name": "nativeHeight"

},

{

"name": "nativeWidth"

}

]

},

{

"name": "java.awt.image.ComponentSampleModel",

"fields": [

{

"name": "pixelStride"

},

{

"name": "scanlineStride"

},

{

"name": "bandOffsets"

},

{

"name": "bankIndices"

},

{

"name": "numBands"

},

{

"name": "numBanks"

}

]

},

{

"name": "sun.awt.image.ByteComponentRaster",

"fields": [

{

"name": "bandOffset"

},

{

"name": "dataOffsets"

},

{

"name": "scanlineStride"

},

{

"name": "pixelStride"

},

{

"name": "data"

},

{

"name": "type"

}

]

},

{

"name": "java.awt.event.MouseWheelEvent",

"methods": [

{

"name": "<init>"

}

]

}

native-image\jni-config.json 找到java.awt.image.IndexColorModel、sun.java2d.InvalidPipeException json节点,里面添加以下内容:

"methods": [

{

"name": "<init>"

}

]

native-image\reflect-config.json 添加以下内容到json末尾节点:

{

"name": "com.github.markusbernhardt.proxy.jna.win.WinHttp",

"allDeclaredConstructors": true,

"allPublicConstructors": true,

"allDeclaredMethods": true,

"allPublicMethods": true

},

{

"name": "sun.java2d.loops.SetDrawLineANY",

"allDeclaredConstructors": true,

"allPublicConstructors": true,

"allDeclaredMethods": true,

"allPublicMethods": true

},

{

"name": "sun.java2d.loops.SetFillRectANY",

"allDeclaredConstructors": true,

"allPublicConstructors": true,

"allDeclaredMethods": true,

"allPublicMethods": true

},

{

"name": "sun.java2d.loops.SetDrawRectANY",

"allDeclaredConstructors": true,

"allPublicConstructors": true,

"allDeclaredMethods": true,

"allPublicMethods": true

},

{

"name": "sun.java2d.loops.SetDrawPolygonsANY",

"allDeclaredConstructors": true,

"allPublicConstructors": true,

"allDeclaredMethods": true,

"allPublicMethods": true

},

{

"name": "sun.java2d.loops.SetDrawPathANY",

"allDeclaredConstructors": true,

"allPublicConstructors": true,

"allDeclaredMethods": true,

"allPublicMethods": true

},

{

"name": "sun.java2d.loops.SetFillPathANY",

"allDeclaredConstructors": true,

"allPublicConstructors": true,

"allDeclaredMethods": true,

"allPublicMethods": true

},

{

"name": "sun.java2d.loops.SetFillSpansANY",

"allDeclaredConstructors": true,

"allPublicConstructors": true,

"allDeclaredMethods": true,

"allPublicMethods": true

},

{

"name": "com.sun.mail.handlers.multipart_mixed",

"methods": [

{

"name": "<init>"

}

]

}

构建文件

        通过以下命令进行正式native编译,编译过程会比较久:

native-image -jar test.jar -H:+ReportExceptionStackTraces --no-fallback --allow-incomplete-classpath -H:ConfigurationFileDirectories=native-image -H:+AddAllCharsets --report-unsupported-elements-at-runtime --enable-url-protocols=https,http

        解释下每个参数的含义:

-H:+ReportExceptionStackTraces

显示构建期间的异常堆栈跟踪

--no-fallback

构建不依赖JVM的native image或显示构建失败

--allow-incomplete-classpath

允许使用不完整的类路径构建,该参数适用于依赖了弱引用的库

-H:ConfigurationFileDirectories=native-image

配置采集到的meta信息的配置文件目录地址

-H:+AddAllCharsets

支持所有字符集,不加该参数会导致中文乱码

--report-unsupported-elements-at-runtime

在运行native image时才报错不受支持的方法和字段,而不是在构建期间报错

--enable-url-protocols=https,http

支持的URL协议,不加该参数会导致无法访问web地址

        以下是构建完成后生成的结果,test.exe文件在70M左右:

补充缺失的文件

        此时直接执行test.exe文件还是会报错,这是因为Native Image在引入依赖文件时漏下了2个配置文件,我们到GraalVM的安装目录内找到 jre\lib下的flavormap.properties和fontconfig.bfc文件,复制到test.exe所在目录的lib子目录下:

        最终在去除掉所有的中间文件后,文件列表如下所示,共计73M左右:

        此时我们双击运行test.exe文件就会得到与执行java -jar test.jar 一样的运行效果了。

简单测试

        我们同时启动Jar和Naive化后的exe文件对比后发现,内存的使用明显减少,启动速度更快,并且不依赖JVM环境。因此使用GraalVM Native Image将Java Swing应用Native化的目标已经达成。

目录
相关文章
|
18天前
|
存储 数据采集 搜索推荐
Java 大视界 -- Java 大数据在智慧文旅旅游景区游客情感分析与服务改进中的应用实践(226)
本篇文章探讨了 Java 大数据在智慧文旅景区中的创新应用,重点分析了如何通过数据采集、情感分析与可视化等技术,挖掘游客情感需求,进而优化景区服务。文章结合实际案例,展示了 Java 在数据处理与智能推荐等方面的强大能力,为文旅行业的智慧化升级提供了可行路径。
Java 大视界 -- Java 大数据在智慧文旅旅游景区游客情感分析与服务改进中的应用实践(226)
|
18天前
|
机器学习/深度学习 数据采集 数据可视化
Java 大视界 -- 基于 Java 的大数据可视化在城市空气质量监测与污染溯源中的应用(216)
本文探讨Java大数据可视化在城市空气质量监测与污染溯源中的创新应用,结合多源数据采集、实时分析与GIS技术,助力环保决策,提升城市空气质量管理水平。
Java 大视界 -- 基于 Java 的大数据可视化在城市空气质量监测与污染溯源中的应用(216)
|
18天前
|
存储 监控 数据可视化
Java 大视界 -- 基于 Java 的大数据可视化在企业生产运营监控与决策支持中的应用(228)
本文探讨了基于 Java 的大数据可视化技术在企业生产运营监控与决策支持中的关键应用。面对数据爆炸、信息孤岛和实时性不足等挑战,Java 通过高效数据采集、清洗与可视化引擎,助力企业构建实时监控与智能决策系统,显著提升运营效率与竞争力。
|
18天前
|
Java 大数据 数据处理
Java 大视界 -- 基于 Java 的大数据实时数据处理在工业互联网设备协同制造中的应用与挑战(222)
本文探讨了基于 Java 的大数据实时数据处理在工业互联网设备协同制造中的应用与挑战。文章分析了传统制造模式的局限性,介绍了工业互联网带来的机遇,并结合实际案例展示了 Java 在多源数据采集、实时处理及设备协同优化中的关键技术应用。同时,也深入讨论了数据安全、技术架构等挑战及应对策略。
|
18天前
|
数据采集 搜索推荐 Java
Java 大视界 -- Java 大数据在智能教育虚拟学习环境构建与用户体验优化中的应用(221)
本文探讨 Java 大数据在智能教育虚拟学习环境中的应用,涵盖多源数据采集、个性化推荐、实时互动优化等核心技术,结合实际案例分析其在提升学习体验与教学质量中的成效,并展望未来发展方向与技术挑战。
|
18天前
|
机器学习/深度学习 人工智能 自然语言处理
Java 大视界 -- Java 大数据机器学习模型在自然语言生成中的可控性研究与应用(229)
本文深入探讨Java大数据与机器学习在自然语言生成(NLG)中的可控性研究,分析当前生成模型面临的“失控”挑战,如数据噪声、标注偏差及黑盒模型信任问题,提出Java技术在数据清洗、异构框架融合与生态工具链中的关键作用。通过条件注入、强化学习与模型融合等策略,实现文本生成的精准控制,并结合网易新闻与蚂蚁集团的实战案例,展示Java在提升生成效率与合规性方面的卓越能力,为金融、法律等强监管领域提供技术参考。
|
18天前
|
存储 人工智能 算法
Java 大视界 -- Java 大数据在智能医疗影像数据压缩与传输优化中的技术应用(227)
本文探讨 Java 大数据在智能医疗影像压缩与传输中的关键技术应用,分析其如何解决医疗影像数据存储、传输与压缩三大难题,并结合实际案例展示技术落地效果。
|
18天前
|
机器学习/深度学习 安全 Java
Java 大视界 -- Java 大数据在智能金融反洗钱监测与交易异常分析中的应用(224)
本文探讨 Java 大数据在智能金融反洗钱监测与交易异常分析中的应用,介绍其在数据处理、机器学习建模、实战案例及安全隐私等方面的技术方案与挑战,展现 Java 在金融风控中的强大能力。
|
18天前
|
机器学习/深度学习 算法 Java
Java 大视界 -- Java 大数据机器学习模型在生物信息学基因功能预测中的优化与应用(223)
本文探讨了Java大数据与机器学习模型在生物信息学中基因功能预测的优化与应用。通过高效的数据处理能力和智能算法,提升基因功能预测的准确性与效率,助力医学与农业发展。
|
18天前
|
机器学习/深度学习 搜索推荐 数据可视化
Java 大视界 -- Java 大数据机器学习模型在电商用户流失预测与留存策略制定中的应用(217)
本文探讨 Java 大数据与机器学习在电商用户流失预测与留存策略中的应用。通过构建高精度预测模型与动态分层策略,助力企业提前识别流失用户、精准触达,实现用户留存率与商业价值双提升,为电商应对用户流失提供技术新思路。

热门文章

最新文章