JDK17环境下Java调用Weka的buildClassifier出现运行时异常如何解决?
问题1:调用buildClassifier出现该异常的原因
该异常和业务代码、数据集无关,是JDK版本与Weka版本的兼容性问题导致的,具体原因如下:
- JDK 9开始引入Java模块化系统(JPMS),对JDK内部非公开API做了强封装限制,JDK 17进一步收紧了反射访问权限:默认情况下,未命名模块(也就是传统无module-info.java的普通Java项目)无法通过反射访问JDK内部的受保护/私有方法。
- 你使用的Weka版本中,包管理器的初始化逻辑会通过反射尝试调用
ClassLoader.defineClass方法注入类,该方法属于java.base模块的java.lang包,默认未对未命名模块开放访问权限,因此触发了InaccessibleObjectException。 - 你的try-catch无法捕获该异常的原因是:该异常是
WekaPackageManager类静态初始化阶段内部抛出且被内部捕获、自行打印堆栈,并没有抛到你调用buildClassifier的业务代码层,因此你的catch块无法捕获到该异常。 - 程序可以正常运行是因为Weka内部捕获该异常后走了降级逻辑,不影响后续分类器的正常构建和使用。
问题2:隐藏异常打印内容的方案
方案1:从根源解决异常(推荐,异常不会再产生)
- 升级Weka到适配高版本JDK的最新稳定版,部分高版本Weka已经重构了包管理器的反射逻辑,不会再触发该权限异常。
- 若不想升级Weka版本,可在JVM启动时添加参数:
--add-opens java.base/java.lang=ALL-UNNAMED,该参数会主动将java.base模块下的java.lang包开放给所有未命名模块,Weka的反射逻辑可以正常执行,不会再抛出该异常。如果用IDEA/Eclipse运行,把这个参数加到运行配置的「VM选项」里即可。 - 也可以通过关闭Weka包管理器的自动加载来避免触发该逻辑:在代码中所有Weka相关调用执行前,添加一行代码设置系统属性:
System.setProperty("weka.packageManager.loadPackages", "false");,不需要修改启动参数,也可以完全避免该异常产生。
方案2:仅屏蔽异常打印(不修改运行逻辑)
该异常是Weka内部打印到标准错误流(System.err)的,如果你不想修改JVM参数也不想调整Weka逻辑,可以临时重定向System.err来屏蔽打印:
import java.io.OutputStream; import java.io.PrintStream; import java.io.IOException; // 第一次调用Weka代码前重定向错误输出 PrintStream defaultErr = System.err; System.setErr(new PrintStream(new OutputStream() { @Override public void write(int b) throws IOException { // 空实现,丢弃所有错误输出 } })); // 调用Weka相关逻辑 or_lens.buildClassifier(lens_data); // 恢复原来的错误输出,避免影响后续自身的错误日志打印 System.setErr(defaultErr);
注意该方案只会屏蔽Weka初始化阶段的错误打印,不会影响程序运行逻辑,但如果Weka初始化过程中有其他真实报错也会被一起丢弃,更推荐使用方案1从根源解决问题。
内容的提问来源于stack exchange,提问作者leesh
相关产品推荐
相关产品推荐

