使用 Unsafe.defineClass 在运行时定义多个 类
Define multiple classes at runtime using Unsafe.defineClass
我正在为我的自定义编程语言开发 REPL。它在编译器之上实现,用于为输入生成字节码并使用 sun.misc.Unsafe.defineClass(String, byte[], int, int, ClassLoader, ProtectionDomain)
方法将其转换为 Class<?>
实例。相关代码如下(省略异常处理等无关部分):
void compileAndLoad(List<ICompilable> compilables)
{
List<Class<?>> classes = ...;
for (ICompilable c : compilables)
{
classes.add(compile(compilable));
}
for (Class<?> c : classes)
{
UNSAFE.ensureClassInitialized(c);
}
}
// CLASS_LOADER = Enclosing.class.getClassLoader()
// PROTECTION_DOMAIN = Enclosing.class.getClassLoader()
Class<?> compile(ICompilable compilable)
{
byte[] bytecode = genBytecode(compilable);
String name = compilable.getFullName() // e.g. 'foo.bar.Baz'
return UNSAFE.defineClass(name, bytes, 0, bytes.length, CLASS_LOADER, PROTECTION_DOMAIN);
}
假设输入需要编译和加载多个 classes。
> class A { interface B { }; func b() = new B { /* anonymous class */ } }
compilables
列表包含内容
[ repl.Result_0, repl.Result_0$A, repl.Result_0$A[=14=], repl.Result_0$A$B ]
repl.Result_0$A
class 依赖于 repl.Result_0$A[=20=]
(匿名) class 和 repl.Result_0$B
class 并在字节码。使用Unsafe
定义时,会出现如下错误:
java.lang.NoClassDefFoundError: repl/Result_0$A$B
at sun.misc.Unsafe.defineClass(Native Method)
at MyClass.compile(MyClass.java:42)
// ... snip
Caused by: java.lang.ClassNotFoundException: repl.Result_0$A$B
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 9 more
我知道这可以通过重新排序列表并首先定义 repl.Result_0$A$B
来解决,但这不是通用的解决方案,因为也可以从 B -> A
中引用。
有没有一种方法可以使用 Unsafe.defineClass
定义和加载多个 classes 而不会导致未解决的 classes 出现验证错误?
您的问题不是Unsafe.defineClass
特有的,而是与程序逻辑有关。每当你“push”多个新的 classes 时,无论你是否使用 ClassLoader.defineClass
or Unsafe.defineClass
,你都必须避免前向引用,这会排除在你的 class依赖项。
对于 Unsafe.defineClass
的实际预期用例,例如反射访问器,有一个明确的依赖方向,因此没问题,但对于您的用例,它不是正确的工具。您必须定义一个 class 加载程序,它允许 JVM 在需要时“pull” classes,例如
void compileAndLoad(List<ICompilable> compilables) {
Map<String,byte[]> compiled = new HashMap<>(compilables.size());
for(ICompilable c: compilables)
compiled.put(c.getFullName(), genBytecode(c));
ClassLoader l = new ClassLoader(CLASS_LOADER) {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] code = compiled.get(name);
if(code == null) throw new ClassNotFoundException(name);
return defineClass(name, code, 0, code.length);
}
};
// the code below this line is questionable; it seems you are relying
// on the side effects of a class initializer
for(String name: compiled.keySet()) try {
Class.forName(name, true, l);
} catch (ClassNotFoundException ex) { throw new AssertionError(ex); }
}
请注意,该代码使用 Class.forName
而不是 loadClass
来像您的原始代码那样强制执行初始化。通常,代码不应该依赖于立即初始化,但是你没有使用加载的 classes 做任何其他事情,所以不清楚用什么来替代。通常的程序是对打算随后使用的 class 使用 loadClass
,然后对它使用 return;初始化(以及依赖项的加载和初始化)将在其实际使用时发生。
进一步注意,整个代码无需使用 Unsafe
…
我正在为我的自定义编程语言开发 REPL。它在编译器之上实现,用于为输入生成字节码并使用 sun.misc.Unsafe.defineClass(String, byte[], int, int, ClassLoader, ProtectionDomain)
方法将其转换为 Class<?>
实例。相关代码如下(省略异常处理等无关部分):
void compileAndLoad(List<ICompilable> compilables)
{
List<Class<?>> classes = ...;
for (ICompilable c : compilables)
{
classes.add(compile(compilable));
}
for (Class<?> c : classes)
{
UNSAFE.ensureClassInitialized(c);
}
}
// CLASS_LOADER = Enclosing.class.getClassLoader()
// PROTECTION_DOMAIN = Enclosing.class.getClassLoader()
Class<?> compile(ICompilable compilable)
{
byte[] bytecode = genBytecode(compilable);
String name = compilable.getFullName() // e.g. 'foo.bar.Baz'
return UNSAFE.defineClass(name, bytes, 0, bytes.length, CLASS_LOADER, PROTECTION_DOMAIN);
}
假设输入需要编译和加载多个 classes。
> class A { interface B { }; func b() = new B { /* anonymous class */ } }
compilables
列表包含内容
[ repl.Result_0, repl.Result_0$A, repl.Result_0$A[=14=], repl.Result_0$A$B ]
repl.Result_0$A
class 依赖于 repl.Result_0$A[=20=]
(匿名) class 和 repl.Result_0$B
class 并在字节码。使用Unsafe
定义时,会出现如下错误:
java.lang.NoClassDefFoundError: repl/Result_0$A$B
at sun.misc.Unsafe.defineClass(Native Method)
at MyClass.compile(MyClass.java:42)
// ... snip
Caused by: java.lang.ClassNotFoundException: repl.Result_0$A$B
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 9 more
我知道这可以通过重新排序列表并首先定义 repl.Result_0$A$B
来解决,但这不是通用的解决方案,因为也可以从 B -> A
中引用。
有没有一种方法可以使用 Unsafe.defineClass
定义和加载多个 classes 而不会导致未解决的 classes 出现验证错误?
您的问题不是Unsafe.defineClass
特有的,而是与程序逻辑有关。每当你“push”多个新的 classes 时,无论你是否使用 ClassLoader.defineClass
or Unsafe.defineClass
,你都必须避免前向引用,这会排除在你的 class依赖项。
对于 Unsafe.defineClass
的实际预期用例,例如反射访问器,有一个明确的依赖方向,因此没问题,但对于您的用例,它不是正确的工具。您必须定义一个 class 加载程序,它允许 JVM 在需要时“pull” classes,例如
void compileAndLoad(List<ICompilable> compilables) {
Map<String,byte[]> compiled = new HashMap<>(compilables.size());
for(ICompilable c: compilables)
compiled.put(c.getFullName(), genBytecode(c));
ClassLoader l = new ClassLoader(CLASS_LOADER) {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] code = compiled.get(name);
if(code == null) throw new ClassNotFoundException(name);
return defineClass(name, code, 0, code.length);
}
};
// the code below this line is questionable; it seems you are relying
// on the side effects of a class initializer
for(String name: compiled.keySet()) try {
Class.forName(name, true, l);
} catch (ClassNotFoundException ex) { throw new AssertionError(ex); }
}
请注意,该代码使用 Class.forName
而不是 loadClass
来像您的原始代码那样强制执行初始化。通常,代码不应该依赖于立即初始化,但是你没有使用加载的 classes 做任何其他事情,所以不清楚用什么来替代。通常的程序是对打算随后使用的 class 使用 loadClass
,然后对它使用 return;初始化(以及依赖项的加载和初始化)将在其实际使用时发生。
进一步注意,整个代码无需使用 Unsafe
…