【发布时间】:2018-07-31 10:47:54
【问题描述】:
请查看下面的修改
我正在尝试创建一个 JShell 实例,它可以让我访问并允许我与创建它的 JVM 中的对象进行交互。这适用于在编译时可用但对于动态加载的类失败的类动态。
public class Main {
public static final int A = 1;
public static Main M;
public static void main(String[] args) throws Exception {
M = new Main();
ClassLoader cl = new URLClassLoader(new URL[]{new File("Example.jar").toURL()}, Main.class.getClassLoader());
Class<?> bc = cl.loadClass("com.example.test.Dynamic");//Works
JShell shell = JShell.builder()
.executionEngine(new ExecutionControlProvider() {
@Override
public String name() {
return "direct";
}
@Override
public ExecutionControl generate(ExecutionEnv ee, Map<String, String> map) throws Throwable {
return new DirectExecutionControl();
}
}, null)
.build();
shell.eval("System.out.println(com.example.test.Main.A);");//Always works
shell.eval("System.out.println(com.example.test.Main.M);");//Fails (is null) if executionEngine is not set
shell.eval("System.out.println(com.example.test.Dynamic.class);");//Always fails
}
}
此外,将DirectExecutionControl 与LocalExecutionControl 交换会得到相同的结果,但我不明白这两个类之间的区别。
如何使 运行时 加载的类可用于此 JShell 实例?
编辑:这个问题的第一部分已经解决,下面是更新的源代码来演示问题的第二部分
public class Main {
public static void main(String[] args) throws Exception {
ClassLoader cl = new URLClassLoader(new URL[]{new File("Example.jar").toURL()}, Main.class.getClassLoader());
Class<?> c = cl.loadClass("com.example.test.C");
c.getDeclaredField("C").set(null, "initial");
JShell shell = JShell.builder()
.executionEngine(new ExecutionControlProvider() {
@Override
public String name() {
return "direct";
}
@Override
public ExecutionControl generate(ExecutionEnv ee, Map<String, String> map) throws Throwable {
return new DirectExecutionControl();
}
}, null)
.build();
shell.addToClasspath("Example.jar");
shell.eval("import com.example.test.C;");
shell.eval("System.out.println(C.C)"); //null
shell.eval("C.C = \"modified\";");
shell.eval("System.out.println(C.C)"); //"modified"
System.out.println(c.getDeclaredField("C").get(null)); //"initial"
}
}
这是预期的输出,如果 JVM 和 JShell 实例 不共享任何内存,但是将 com.example.test.C 直接添加到项目而不是动态加载它改变结果如下:
shell.eval("import com.example.test.C;");
shell.eval("System.out.println(C.C)"); //"initial"
shell.eval("C.C = \"modified\";");
shell.eval("System.out.println(C.C)"); //"modified"
System.out.println(c.getDeclaredField("C").get(null)); //"modified"
为什么在运行时加载的类不共享 JVM 和 JShell 实例 之间的内存?
编辑 2:问题似乎是由不同的类加载器引起的
在上述示例的上下文中执行以下代码:
System.out.println(c.getClassLoader()); //java.net.URLClassLoader
shell.eval("System.out.println(C.class.getClassLoader())"); //jdk.jshell.execution.DefaultLoaderDelegate$RemoteClassLoader
shell.eval("System.out.println(com.example.test.Main.class.getClassLoader())"); //jdk.internal.loader.ClassLoaders$AppClassLoader
这表明,同一个类com.example.test.C 由两个不同的类加载器加载。是否可以将类添加到 JShell 实例而不再次加载它?如果不是,为什么已经加载了静态加载的类?
【问题讨论】:
-
您使用哪个 IDE 运行您的代码?
-
我尝试了两个 Netbeans 并直接运行它。
-
这个不可否认的非常有趣的练习有最终目标吗?
-
好吧,它现在实际上正在工作,请参阅下面的我的答案。我这样做是为了调试 Minecraft 服务器插件,但这主要是一个有趣的练习。我希望该解决方案对其他想要了解 JShell 如何与启动它的 JVM 交互的人有所帮助。
标签: java java-9 dynamic-class-loaders jshell