ko1haha
V2EX  ›  Java

让 Java 再次伟大,没有人比我更懂得如何打包!(分享一个四两拨千斤的多 jar 打包 exe 方式)

  •  
  •   ko1haha · Jun 29 · 3009 views

    Java 确实是内存不高效的。同样的内存,fultter / dart 能够创建流畅的应用,而 Java 要么卡成 ppt ,要么疯狂吃内存。

    分享测试一个虚拟机内存回收效率的方法(大家应该都会吧:):win 下使用 procgov 假装本机只有 100 mb 内存,再运行 gui 程序,测试卡不卡。

    结果,javafx 卡成 ppt ,flutter 写的桌面 app 却依然流畅,几乎没有卡顿!(例子:dart 写的 myune_music 存在严重的内存泄漏,稍微切换内存狂飙 300mb+,然而限制 100mb 后,依然流畅,神奇吧!)

    这说明 flutter / dart 的虚拟机比 java 更高级 —— 即使再内存泄露的情况下。

    人才不断涌入新的领域,Java 已经成熟到可以封印起来装裱,成熟到可以使用 java8 一万年,新的改进似乎九牛一毛,再也不能从根本上提升。

    但当我从 64 位的 java8 切换到 32 位的 java8 ,内存需求立马减半,即使限制 100mb 内存,也没有那么卡了。

    这给我一点信心。我还是很喜欢 javafx ,也曾升级到新的 open jdk ,打包方式太复杂,而且几乎没有区别,尤其是浏览器内核,所以又回到 java8 并锁死。

    那么,如何打包分享给其他用户呢?

    搜遍全网,似乎没有好的方案?

    • Launch4j: 提供把单个 jar 追加到预制的启动器后面的 blacksmith 。多 jar 需要一个个填写十分麻烦。
    • jsmooth: 也是预制启动器.exe ,可以更方便地添加多个 jar (相对路径)作为启动参数。

    这些方案的本质是启动器。因为 java 程序的本质就是调用上位开发者提供的虚拟机.exe 运行编译后的字节码。

    那我自己写个启动器好了!编不了虚拟机,我还编不了启动器?小样,何须他人预制!

    先说我自己如何自用一些 java 小工具的:使用 idea 编译后,写个 bat/AHK/Python 调用 java.exe 运行编译好的 class 。

    那么我的启动器就可以封装这条启动指令。

    图像( AHK 写的启动器 GUI ):

    没有检测到 jvm 的时候,提供下载链接。启动成功后,保存快捷方式,就能直接运行 javaw.exe ,不再需要启动器,干掉中间层!

    别人还在研究怎么使用他人预制的启动器.exe ,我已经干掉启动器了,启动器就是个跳板,用于导出快捷方式,也可以右键固定开始菜单,几乎没啥问题!

    就用这个方法发布些小工具吧,让 Java 再次伟大,从占领小工具 && 小游戏开始!

    47 replies    2026-07-01 10:14:54 +08:00
    HTravel
        1
    HTravel  
       Jun 29   ❤️ 1
    都 2026 年了,给自己写个工具居然还用 JRE8 ?
    而且居然一个启动器都值得宣传?这些年但凡你认真学点 C/C++的皮毛,这压根简单到不是什么事。更别说现在有 AI
    Kinnice
        2
    Kinnice  
       Jun 29
    看了眼时间,现在居然是 2026 :)
    JAVA 还是太老资历了。
    Yasuke
        3
    Yasuke  
       Jun 29
    但凡你用点新的
    wu00
        4
    wu00  
       Jun 29   ❤️ 1
    j8 还是太能打了
    yuaotian
        5
    yuaotian  
       Jun 29
    ???不是兄弟,现在都支持二进制打包了,你跟我来 jre ?
    126ium
        6
    126ium  
       Jun 29 via Android
    真是梦回 2006 。用一句当年比较流行的话回你吧:楼主是火星人,还是快回火星吧,地球是很危险滴
    zhangshaohan
        7
    zhangshaohan  
       Jun 29
    做 exe 直接用 QT 吧,我现在已经用 QT 开发很多软件了,甚至完全不会 C++和 QT ,比如这个: https://gitee.com/aiexporter/paster
    shuangbiaog
        8
    shuangbiaog  
       Jun 29
    当年写毕设用的 javafx ,打包是用一个叫 javapackager 的 gradle/maven 插件
    colincat
        9
    colincat  
       Jun 29
    我支持你,op 加油~
    cutecore
        10
    cutecore  
       Jun 29
    为什么不试试 graalvm 呢,
    msg7086
        11
    msg7086  
       Jun 29
    还以为是说用 JDK 25 GraalVM 直接编译到二进制呢。是我高估了。
    nealHuang
        12
    nealHuang  
       Jun 29
    还以为回到 10 年前了
    liuliuliuliu
        13
    liuliuliuliu  
    PRO
       Jun 29
    有这功夫不如试试 .net 了
    ko1haha
        14
    ko1haha  
    OP
       Jun 29
    @HTravel 我的工具从 2017 年写到现在而已。。
    ko1haha
        15
    ko1haha  
    OP
       Jun 29
    @liuliuliuliu
    @cutecore graalvm 听说过,但是没见过。有没有简易上手可以立马进行测评的 graalvm 程序? 我发现 java 的东西都藏得好深,都要用 idea ,没有直接可以运行的 demo 。
    ko1haha
        16
    ko1haha  
    OP
       Jun 29
    @HTravel 这里只是提供一个之前几乎没人提的思路,纯技术性讨论,并没有宣传我的任何产品(所有截图已预先打码,防的就是你这种)
    ko1haha
        17
    ko1haha  
    OP
       Jun 29
    @zhangshaohan QT 的界面有限制。更何况你应该用的是 pyqt ? QT 还是太老牌了,性能没问题,但不如试试新东西比如 flutter 。
    ko1haha
        18
    ko1haha  
    OP
       Jun 29
    @126ium The jre-8u211-windows-i586.exe (Java Runtime Environment 8 Update 211 for 32-bit Windows) was officially released on April 16, 2019 。你让 oracle 整个大公司回火星?无知。
    ko1haha
        19
    ko1haha  
    OP
       Jun 29
    @yuaotian 二进制打包是什么,没见过,有歧义。
    mmdsun
        20
    mmdsun  
       Jun 29 via iPhone
    好早写 spring boot 已经是 Native 打包了,Windows 上直接 exe 运行跑、Linux 直接可执行文件。

    https://docs.spring.io/spring-boot/how-to/native-image/developing-your-first-application.html
    kestrelBright
        21
    kestrelBright  
       Jun 29 via iPhone
    没有无用的工具 总有适合的人
    ko1haha
        22
    ko1haha  
    OP
       Jun 29
    @mmdsun Native 打包应该不是纯 Java Jvm 了,应该有所牺牲,不知性能如何。还是那句话,没有一个可以快速测试的例程.exe ,还是要苦哈哈打开 idea,然后不断往我 c 盘下载一些东西,性能仍是个谜。。
    ruanimal
        23
    ruanimal  
       Jun 29
    得配一下 TLP ( The Linux Power Manager )是 Linux 上最受欢迎的高级电源管理工具之一
    0x64
        24
    0x64  
       Jun 29
    你们 j8 确实厉害,佩服佩服
    sheeta
        25
    sheeta  
       Jun 29
    试试用 openj9 jvm 看看,内存应该还能降
    zhangshaohan
        26
    zhangshaohan  
       Jun 29
    @ko1haha #17 我用的原生 QT
    jinwiforz
        27
    jinwiforz  
       Jun 29
    java 真难受
    5had0w
        28
    5had0w  
       Jun 29
    graalvm 打包 exe 不会很大,直接可以运行,不需要 java 环境
    ko1haha
        29
    ko1haha  
    OP
       Jun 29
    @mmdsun 刚刚试了,电脑里就有个 GraalVm ,找出来 bin/native-image.cmd ,然后转换一个 100kb 的 jar 为 native exe 。cpu 狂飙 100 ,然后生成一个 19 mb 的 exe ,这道没什么,关键不能运行,读取参数错误,存在兼容性问题。

    doc : https://www.graalvm.org/latest/reference-manual/native-image/guides/build-native-executable-from-jar/

    测试的是 ManifestEditor.jar : https://github.com/WindySha/ManifestEditor

    说白了,这玩意儿就是给服务器省带宽用的,不是给普通人开发应用程序用的。使用 native-image 在编译时需要更多的计算机资源,以及更多的测试精力,去处理各种各样的兼容性问题。。

    总之:长远看 graalvm 能给大公司省钱,就是周期不知道有多长。。
    HTravel
        30
    HTravel  
       Jun 29
    @ko1haha Java 程序打包成 exe ,这都 20 多年前的老问题了,算什么没人提的思路。我给你一个 win32ASM 汇编完整版的思路,我自己都用十几年了:
    1 、检测并自动安装私有 JRE ,不影响用户计算机上的 JRE 环境。原理:先安装一遍 JRE ,然后用 InnoSetup 将安装目录制作成私有 JRE 的 exe 安装包,私有 JRE 的本质,就是安装目录、注册表信息都放在另一个路径下即可。

    2 、将上述私有 JRE exe 安装包作为启动器 exe 文件的一个资源项,也可以放在公网或内网,然后引用该连接。

    3 、这个启动器用 win32ASM 汇编写,检测私有 JRE 是否已安装,没有就从自己资源项释放并静默安装,或从配置文件中所写的 URL 下载并静默安装。然后根据配置文件所写的参数格式,组织用户通过命令行或 Explorer 传递过来的各参数,以 CreateProgress 的方式调用 java.exe 或 javaw.exe ( GUI 程序)即可。甚至还有进程内的方案,就是用 LoadLibrary 调用 jvm.dll
    lesismal
        31
    lesismal  
       Jun 29
    > 总之:长远看 graalvm 能给大公司省钱,就是周期不知道有多长。。

    别人换 golang 、rust 、zig 了,java 这种该淘汰就淘汰吧,淘汰了就省钱了,但历史业务积累太多,所以如 OP 所说:“就是周期不知道有多长。。”
    ashuai
        32
    ashuai  
       Jun 29
    java 生态一般不搞 all in one 吧,微服务的话,一个个换就行了,其实就是想不想的问题
    huyangq
        33
    huyangq  
       Jun 29
    额 java 自带的就可以打包 javafx
    就是 jlink+jpackage
    sir283
        34
    sir283  
       Jun 30
    exe ?那我为什么不选择 c#+winform ? c#好歹是微软亲儿子,而且从 Windows8 开始,系统直接内置了,编译出来的程序体积也很小,比 Java 好多了,c#甚至能几乎无损直接调用 C/C++的库,也比 Java 方便的多。
    a0210077
        35
    a0210077  
       Jun 30
    老项目 java8 继续呆着,新项目建议至少上 java17 ,激进点 java25 有虚拟线程

    "没有检测到 jvm 的时候,提供下载链接。启动成功后,保存快捷方式,就能直接运行 javaw.exe ,不再需要启动器,干掉中间层!"
    公司内部用跑过去装个 JRE ,生产环境"提供下载链接"有点过分了,但本质还是装了个 JRE 而不是"干掉中间层"

    "就用这个方法发布些小工具吧,让 Java 再次伟大,从占领小工具 && 小游戏开始!"
    小工具写 bat/ps1/python 脚本都比 JAVA 来得轻量,GUI 界面不是 JAVA 擅长的领域(内置界面丑),小游戏有 h5 游戏引擎有浏览器就能玩。恐怕 OP 的美梦要破灭了
    alexluo1
        36
    alexluo1  
       Jun 30
    老 j8 这么难杀吗
    keenkiller
        37
    keenkiller  
       Jun 30
    2026 年了,用 AI 把你写的东西翻译成 QT 就完事
    msg7086
        38
    msg7086  
       Jun 30
    #18 连 Oracle 都已经在往 JDK25 迁移了,你还死抱着 8 啊。
    ko1haha
        39
    ko1haha  
    OP
       Jun 30
    @HTravel 我表达的思路根本就不是如何写启动器,别来显摆你会汇编,而且无图,说谁都会。我的思路是写个启动器,然后用启动器创建方式啊,这之前根本没人提。
    ko1haha
        40
    ko1haha  
    OP
       Jun 30
    @a0210077 Javafx 不丑。"干掉中间层"你没理解我意思,中间层的意思是第三方的启动器 wrapper ,启动器是个 exe ,结果启动的是 java.exe ,中间层是这个意思懂了吧。

    不过也有 wrapper 本身就是 jre ,比如 WinRun4J 。

    还有内网"提供下载链接"怎么就过分了?实在不行你可以用 U 盘拷贝安装包。

    bat/ps1/python 写的小工具只能自用吧,部署到自己的另外一台电脑都成问题。小游戏确实 h5 。
    HTravel
        41
    HTravel  
       23h 47m ago
    @ko1haha V 站截图我没搞明白浏览器中怎么玩。不过我写的汇编有自己的风格,与 AI 生成的不一样。把我主过程拷贝一份贴出来就行了。

    _main proc
    local @szAppProcessId[24]:DWORD
    local @szAppLauncherProcessId[24]:DWORD
    local @szStrA2W[MAX_PATH_X8]:WORD
    local @length:DWORD
    local @pos:DWORD

    invoke RtlZeroMemory, addr @szAppProcessId, sizeof @szAppProcessId
    invoke RtlZeroMemory, addr @szAppLauncherProcessId, sizeof @szAppLauncherProcessId
    invoke RtlZeroMemory, addr @szStrA2W, sizeof @szStrA2W

    invoke _IsWow64
    ;初始化当前目录、ini 文件路径
    invoke DoInit

    ;设置工作目录
    invoke SetCurrentDirectoryW, addr szExeDirW

    invoke GetParameters


    invoke lstrcpyW, addr szExecutedCmdStrW, addr szIniCommandLineW
    .if bIsLogEnabled
    invoke StdOutRichText3AAW, 11B, CTXT("[Log] "), DW_CONSOLE_TEXT_ATTR_DEFAULT, CTXT("Before {var} expand: "), 011B, addr szExecutedCmdStrW
    invoke StdOut, addr SZ_NEW_LINEA
    .endif

    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{EXE_DIR}"), addr szExeDirW
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{EXE_NAME}"), addr szExeNameWithoutExtW
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{APP_PATH}"), addr szAppRealPathW
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{QUOTATION_MARK}"), addr SZ_QUOTATION_MARK_CHARW
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{%0}"), addr szCommandLineParameter0W
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{%1}"), addr szCommandLineParameter1W
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{%2}"), addr szCommandLineParameter2W
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{%3}"), addr szCommandLineParameter3W
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{%4}"), addr szCommandLineParameter4W
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{%5}"), addr szCommandLineParameter5W
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{%6}"), addr szCommandLineParameter6W
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{%7}"), addr szCommandLineParameter7W
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{%8}"), addr szCommandLineParameter8W
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{%9}"), addr szCommandLineParameter9W
    invoke StrReplaceWAW, addr szExecutedCmdStrW, CTXT("{%*}"), addr szCommandLineParametersW

    .if bIsLogEnabled
    invoke StdOutRichText3AAW, 11B, CTXT("[Log] "), DW_CONSOLE_TEXT_ATTR_DEFAULT, CTXT("After {var} expand: "), 10B, addr szExecutedCmdStrW
    ;invoke MultiByteToWideChar, CP_ACP, 0, addr szExecutedCmdStrW, -1, addr szExecutedCmdStrW, MAX_PATH_X8
    invoke StdOut, addr SZ_NEW_LINEA
    invoke StdOut, addr SZ_NEW_LINEA
    .endif
    invoke SetConsoleOutputCP, 65001
    invoke SetConsoleCP, 65001
    invoke CreateProcessW, NULL, addr szExecutedCmdStrW, NULL, NULL, NULL, NORMAL_PRIORITY_CLASS, NULL, NULL, addr stStartUp, addr stProcInfo
    .if eax
    mov bHasChildProcess, TRUE
    .if bIsLogEnabled
    invoke wsprintf, addr @szAppProcessId, CTXT("%u"), stProcInfo.dwProcessId
    invoke StdOutRichText3LnA, 11B, CTXT("[Log] "), DW_CONSOLE_TEXT_ATTR_DEFAULT, CTXT("Child process ID: "), 10B, addr @szAppProcessId
    invoke StdOut, addr SZ_NEW_LINEA
    ; invoke StdOut, addr SZ_NEW_LINEA
    .endif
    invoke WaitForSingleObject, stProcInfo.hProcess, INFINITE
    invoke CloseHandle, stProcInfo.hProcess
    invoke CloseHandle, stProcInfo.hThread
    .else
    mov bHasChildProcess, FALSE
    ifdef WINDOWS
    invoke MessageBoxAW, CTXT("[Error] Cannot launch App: "), addr szExecutedCmdStrW, CTXT("[Error] Cannot launch App")
    endif
    invoke StdOutRichText3LnAAW, 100B, CTXT("[Error] "), DW_CONSOLE_TEXT_ATTR_DEFAULT, CTXT("Cannot launch App: "), 100B, addr szExecutedCmdStrW
    invoke StdOutLastErrorMessage
    .endif
    ret
    _main endp
    ko1haha
        42
    ko1haha  
    OP
       20h 27m ago
    @HTravel 用图床网站比如 imgur.com ,v 站有教程
    ko1haha
        43
    ko1haha  
    OP
       20h 21m ago
    @huyangq 官方方法确实是 jlink+jpackage ,需要 jdk 14 。

    现实中有没有使用 jpackage 打包的应用程序呢?找了一圈没找到,全是源代码和教程。jpackage 打包后其实是携带了一个 runtime ,我怀疑外面的 exe 也是启动器,调用 runtime 里面的 javaw.exe 。

    我电脑上偶尔使用的 java gui 应用程序,好像只有 XDM 下载器。我看了下他的方法,和我说的差不多,也是一个快捷方式,调用 java-runtime 文件夹下的运行时。
    a0210077
        44
    a0210077  
       20h 17m ago
    @ko1haha
    用一个 wrapper 顶替了原来 wrapper 的位置,还是没干掉……
    "内网提供下载链接"额外增加了一个步骤,让程序自己检测再下载会产生不稳定,如下载太久/电脑太卡/硬盘满了/没有写权限等稀奇古怪的问题,用户会觉得程序写的烂给个差评。这个步骤可以附带在安装包,或者预装到机器上,出现问题反馈更及时
    bat/ps1/python 和 java 写的工具没有本质的区别,都是调用系统指令干活
    ko1haha
        45
    ko1haha  
    OP
       15h 45m ago
    @a0210077 快捷方式直达 javaw.exe ,哪来的 wrapper ?
    不提供提供下载链接,那么你们的软件都是随系统光盘预装的?军工企业啊,请你遵守保密规范,不要频繁浏览 V2EX 。
    好话都让你说完了,我还能说什么呢。
    a0210077
        46
    a0210077  
       4h 55m ago
    @ko1haha "不提供提供下载链接,那么你们的软件都是随系统光盘预装的?军工企业啊,请你遵守保密规范,不要频繁浏览 V2EX 。"
    不是这个意思,带上 JRE 的本地安装包规避下载失败/下载缓慢的风险
    a0210077
        47
    a0210077  
       4h 49m ago
    @ko1haha
    如果工具给网友用,一键下载+安装非常完美,用不了最多留言发邮件。
    如果给同事用,失败了大概率会找你:x 工,你给那个程序用不了,帮忙看看。让软件能跑起来你的职责,你必须去处理,还会打断工作节奏
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   5467 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 153ms · UTC 07:04 · PVG 15:04 · LAX 00:04 · JFK 03:04
    ♥ Do have faith in what you're doing.