如何在GraalVM 21无控制台模式下获取屏幕尺寸
在GraalVM无控制台模式下检测屏幕尺寸的解决方案
问题概述
在GraalVM原生无控制台模式下(通过editbin修改子系统为WINDOWS),调用java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment()时程序静默崩溃,日志仅记录到初始化前的内容;但Jar模式、GraalVM原生控制台模式均能正常运行。
原因分析
- 子系统兼容性问题:事后用
editbin修改程序子系统为WINDOWS,会破坏GraalVM原生程序的初始化逻辑——控制台模式与WINDOWS子系统的程序入口、UI环境初始化流程不同,AWT依赖的底层Windows GDI资源无法正常加载。 - AWT线程模型要求:AWT组件的初始化必须在**事件调度线程(EDT)**中执行,直接在main线程调用会触发未定义行为,在无控制台环境下表现为静默崩溃。
- 异常捕获缺失:无控制台模式下
System.out无效,且未配置全局异常处理器,导致崩溃时无法记录错误信息。
解决方案
1. 直接编译无控制台原生程序
放弃事后修改子系统的方式,用GraalVM原生镜像的-H:Subsystem=WINDOWS参数直接编译,让GraalVM自动适配WINDOWS子系统的初始化逻辑:
修改compileNative.bat为:
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" native-image -H:ConfigurationFileDirectories=config -H:Subsystem=WINDOWS -march=compatibility --enable-url-protocols=http,https --enable-preview --add-modules jdk.incubator.vector -jar target\com.tugalsan.blg.gvm.graphicsdevice-1.0-SNAPSHOT-jar-with-dependencies.jar com.tugalsan.blg.gvm.graphicsdevice.noconsole
此时无需再执行compileNoConsole.bat。
2. 在EDT线程中初始化AWT组件
AWT的图形相关操作必须在事件调度线程中执行,修改Main.java的main方法:
package com.tugalsan.blg.gvm.graphicsdevice; import java.awt.EventQueue; import java.awt.GraphicsEnvironment; import static java.lang.System.out; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.stream.Collectors; public class Main { public static void log(CharSequence funcName, Object... oa) { try { var lstStr = Arrays.stream(oa) .map(o -> String.valueOf(o)) .collect(Collectors.toCollection(ArrayList::new)); var str = funcName + " -> " + lstStr + "\n"; Files.writeString(file, str, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND); } catch (Exception e) { throw new RuntimeException(e); } } final public static Path file = FileSystems.getDefault().getPath(Main.class.getPackageName() + ".log").toAbsolutePath(); public static void main(String... s) { // 配置全局未捕获异常处理器,记录崩溃信息 Thread.setDefaultUncaughtExceptionHandler((t, e) -> { try { var errMsg = "Uncaught exception in thread " + t.getName() + ":\n"; Files.writeString(file, errMsg, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND); var writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND); e.printStackTrace(new PrintWriter(writer)); writer.close(); } catch (IOException ex) { ex.printStackTrace(); } }); try { Files.deleteIfExists(file); log("main", "#1"); // 在事件调度线程中初始化GraphicsEnvironment EventQueue.invokeLater(() -> { try { var localGraphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); log("main", "#2"); // 获取屏幕尺寸示例 var defaultScreen = localGraphicsEnvironment.getDefaultScreenDevice(); var screenBounds = defaultScreen.getDefaultConfiguration().getBounds(); log("main", "Screen size:", screenBounds.width + "x" + screenBounds.height); } catch (Exception e) { log("main", e); } }); // 等待EDT执行完成(若为后台服务,需调整线程生命周期逻辑) Thread.sleep(1000); } catch (Exception e) { log("main", e); } } }
3. 确保Native Image配置完整
重新运行native-image-agent生成配置,确保AWT相关类、JNI方法被正确捕获:
java -agentlib:native-image-agent=config-output-dir=config --enable-preview --add-modules jdk.incubator.vector -jar target/com.tugalsan.blg.gvm.graphicsdevice-1.0-SNAPSHOT-jar-with-dependencies.jar
生成的config目录会包含AWT所需的反射、JNI、资源等配置,保证原生编译时不会遗漏必要组件。
验证效果
编译完成后运行com.tugalsan.blg.gvm.graphicsdevice.noconsole.exe,日志文件会完整记录初始化流程和屏幕尺寸信息,程序不再静默崩溃。
内容的提问来源于stack exchange,提问作者Tugalsan Karabacak
相关产品推荐
相关产品推荐

