同样的源码两次构建,SHA-256 却不一样,问题出在哪?这篇逐层定位:javac 的输出、jar 工具的时间戳与条目顺序、Maven 的 outputTimestamp,以及换 JDK 版本带来的差异,每一层都用真实的哈希对比。
同一份源码编译两次(间隔 1.2 秒),每个 .class 的 SHA-256 前 16 位: {'B.class': 'a61ab07b52b89ea4', 'A.class': 'f2615c98b0b29b28', 'Main.class': '514b3d88eaf6c4b0'}
两次的 class 文件完全相同 = True
JDK 17 编译(--release 17)与 JDK 21 编译(--release 17)的 class 文件相同 = False ; {'B.class': True, 'A.class': True, 'Main.class': False}
Main.class 在 JDK 17 与 JDK 21(--release 17)下的 javap -v 差异(用同一个 javap 读,去掉路径与校验和行): ['- #44 = MethodHandle 6:#45 // REF_invokeStatic java/lang/invoke/StringConcatFactory.', '- #45 = Methodref #46.#47 // java/lang/invoke/StringConcatFactory.makeConcatWithCon', '- #46 = Class #48 // java/lang/invoke/StringConcatFactory', '- #47 = NameAndType #27:#49 // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandl', '- #48 = Utf8 java/lang/invoke/StringConcatFactory', '- #49 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/inv', '- #50 = String #51 // \\u0001\\u0001', '- #51 = Utf8 \\u0001\\u0001'] (共 20 行)
两者的方法字节码(javap -c,常量池序号抹成 #N)是否一致 = True ;javap -v 的常量池条目数: 57 (JDK 17)vs 57 (JDK 21 --release 17)
JDK 21 默认(--release 21)与 --release 17 的 class 文件相同 = False (版本号不同:A.class 主版本 65 vs 61) 同一份 class、间隔 2.2 秒 jar 两次:a.jar 25f786137d36a91f,b.jar f8b708343dcb724f,相同 = False
条目的时间戳(前 3 条):a.jar [('META-INF/', '2026-10-03 19:25:16'), ('META-INF/MANIFEST.MF', '2026-10-03 19:25:16'), ('demo/', '2026-10-03 19:25:12')] ;b.jar [('META-INF/', '2026-10-03 19:25:18'), ('META-INF/MANIFEST.MF', '2026-10-03 19:25:18'), ('demo/', '2026-10-03 19:25:12')]
加 --date=2024-01-01T00:00:00Z 同样间隔 2.2 秒 jar 两次:c.jar 058dc0ec908ecff4,d.jar 058dc0ec908ecff4,相同 = True;时间戳 [('META-INF/', '2024-01-01 00:00:00'), ('META-INF/MANIFEST.MF', '2024-01-01 00:00:00')]
加了 --date,但按不同的顺序把 class 加入:o1.jar bdc04d1e1b3c37af,o2.jar cc95c511f886a28a,相同 = False
条目顺序 o1: ['demo/A.class', 'demo/B.class', 'demo/Main.class'] ;o2: ['demo/Main.class', 'demo/B.class', 'demo/A.class']
manifest 里的 Created-By:JDK 21 的 jar 工具 = ['Created-By: 21.0.12.1 (Homebrew)'] ;JDK 17 的 jar 工具 = ['Created-By: 17.0.20.1 (Eclipse Adoptium)'] 没有设置 outputTimestamp:两次构建(间隔 2.2 秒)jar 的 SHA-256 f6e05b5da5e33980 / 27b48565ed3e61e3,相同 = False
设置 project.build.outputTimestamp=2024-01-01T00:00:00Z,第二次还把源文件的修改时间改成 1 小时后:d6fae28f279cbec1 / d6fae28f279cbec1,相同 = True
条目: [('META-INF/', '2024-01-01 00:00:00'), ('META-INF/MANIFEST.MF', '2024-01-01 00:00:00'), ('demo/', '2024-01-01 00:00:00'), ('META-INF/maven/', '2024-01-01 00:00:00')]
manifest: ['Manifest-Version: 1.0', 'Created-By: Maven JAR Plugin 3.4.2', 'Build-Jdk-Spec: 21', 'Main-Class: demo.Main']
同样的配置改用 JDK 17 构建:c9bc771134dafc20,与 JDK 21 构建的相同 = False
两个 manifest 的差异: ['Build-Jdk-Spec: 17'] ↔ ['Build-Jdk-Spec: 21']
去掉 Maven 描述文件(addMavenDescriptor=false)后 JDK 17 / 21 构建:70ad388be293e62d / aeaee37181e6fa10,相同 = False
再去掉默认 manifest 条目(addDefaultEntries=false)后 JDK 17 / 21 构建:261b2a0a4f3927ab / 5b025bfe7612e0ff,相同 = False
内容不同的条目: ['demo/Main.class']