如何配置Java 17程序反射访问Java 19虚拟线程API
我要发布兼容Java 17的程序,同时通过反射检测当前JVM是否支持Thread.ofVirtual().name("abc").factory()来使用Java 19的虚拟线程特性,但Java模块机制限制了反射访问内部API。已经在module-info.java中添加requires java.base,但问题依然存在,求可行的JVM参数或模块配置方案。
JShell测试报错
| 欢迎使用 JShell -- 版本 19.0.2 | 输入 /help intro 查看介绍 jshell> Thread.class.getMethod("ofVirtual") ...> .invoke(null) ...> .getClass() ...> .getMethod("name", String.class, Long.TYPE) ...> .setAccessible(true) | 异常 java.lang.reflect.InaccessibleObjectException: 无法使 public java.lang.Thread$Builder$OfVirtual java.lang.ThreadBuilders$VirtualThreadBuilder.name(java.lang.String,long) 可访问: 模块 java.base 未向未命名模块 @30dae81 打开 java.lang | 在 AccessibleObject.throwInaccessibleObjectException (AccessibleObject.java:387) | 在 AccessibleObject.checkCanSetAccessible (AccessibleObject.java:363) | 在 AccessibleObject.checkCanSetAccessible (AccessibleObject.java:311) | 在 Method.checkCanSetAccessible (Method.java:201) | 在 Method.setAccessible (Method.java:195) | 在 (#1:5)
Exception java.lang.reflect.InaccessibleObjectException: 无法使 public java.lang.Thread$Builder$OfVirtual java.lang.ThreadBuilders$VirtualThreadBuilder.name(java.lang.String,long) 可访问: 模块 java.base 未向未命名模块 @30dae81 打开 java.lang
模块配置文件(添加requires java.base后)
// src/main/java/module-info.java module test_20230518_ { requires java.base; }
测试代码
// src/main/java/a/A.java package a; import java.lang.reflect.Method; import java.util.Arrays; import java.util.concurrent.ThreadFactory; public class A { public static void main(String[] args) { ThreadFactory threadFactory = tf(); threadFactory.newThread(() -> System.out.println("hi from " + Thread.currentThread().getName())); } private static ThreadFactory tf() { Method[] methods = Thread.class.getMethods(); boolean haveVirtual = Arrays.stream(methods) .anyMatch(m -> m.getName().equals("ofVirtual") && m.getParameterCount() == 0); if (haveVirtual) { try { Object b = Thread.class.getMethod("ofVirtual") .invoke(null); b = b.getClass().getMethod("name", String.class, Long.TYPE) .invoke(b, "prefix-", (long) 1); b = b.getClass().getMethod("factory") .invoke(b); return (ThreadFactory) b; } catch (Throwable t) { throw new RuntimeException(t); } } else { return Thread::new; } } }
运行报错
Exception in thread "main" java.lang.RuntimeException: java.lang.IllegalAccessException: 类 a.A 无法访问类 java.lang.ThreadBuilders$VirtualThreadBuilder(位于模块 java.base 中)的带有修饰符 "public volatile" 的成员 at a.A.tf(A.java:31) at a.A.main(A.java:9) Caused by: java.lang.IllegalAccessException: 类 a.A 无法访问类 java.lang.ThreadBuilders$VirtualThreadBuilder(位于模块 java.base 中)的带有修饰符 "public volatile" 的成员 at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:420) at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:709) at java.base/java.lang.reflect.Method.invoke(Method.java:569) at a.A.tf(A.java:26) ... 1 more
class a.A 无法访问类 java.lang.ThreadBuilders$VirtualThreadBuilder(位于模块 java.base 中)的带有修饰符 "public volatile" 的成员
解决方案
方案1:通过JVM参数开放模块访问
虚拟线程的ThreadBuilders$VirtualThreadBuilder属于java.base模块的java.lang包,需要通过JVM参数让模块向你的程序开放反射权限:
- 如果程序是模块化的(有
module-info.java),添加参数:
其中--add-opens java.base/java.lang=test_20230518_test_20230518_是你定义的模块名。 - 如果程序是非模块化的(运行时在未命名模块),添加参数:
--add-opens java.base/java.lang=ALL-UNNAMED
另外,Java 19的虚拟线程是预览特性,运行时必须添加:
--enable-preview
完整运行命令(模块化程序示例):
java --enable-preview --add-opens java.base/java.lang=test_20230518_ -cp <你的类路径> a.A
方案2:优化反射逻辑,避免访问内部类
问题核心是你直接反射调用了内部实现类ThreadBuilders$VirtualThreadBuilder的方法,改成操作公开的Thread.Builder.OfVirtual接口即可绕过模块限制,不需要额外JVM参数:
修改tf()方法如下:
private static ThreadFactory tf() { Method[] methods = Thread.class.getMethods(); boolean haveVirtual = Arrays.stream(methods) .anyMatch(m -> m.getName().equals("ofVirtual") && m.getParameterCount() == 0); if (haveVirtual) { try { // 获取公开的Thread.ofVirtual()方法,返回Thread.Builder.OfVirtual接口实例 Method ofVirtualMethod = Thread.class.getMethod("ofVirtual"); Object builder = ofVirtualMethod.invoke(null); // 通过公开接口类获取name方法 Class<?> builderInterface = Class.forName("java.lang.Thread$Builder$OfVirtual"); Method nameMethod = builderInterface.getMethod("name", String.class, long.class); builder = nameMethod.invoke(builder, "prefix-", 1L); // 通过公开接口类获取factory方法 Method factoryMethod = builderInterface.getMethod("factory"); Object factory = factoryMethod.invoke(builder); return (ThreadFactory) factory; } catch (Throwable t) { throw new RuntimeException(t); } } else { return Thread::new; } }
注意事项
- Java 19/20的虚拟线程是预览特性,Java 21才正式成为标准特性。兼容Java 17时,必须通过反射检测API是否存在,避免低版本JVM直接加载相关类导致
ClassNotFoundException。 --add-opens属于打破模块封装的非常规手段,未来JDK版本可能调整,优先推荐方案2的反射逻辑优化。
内容的提问来源于stack exchange,提问作者Dave Ankin

