java - 使用 Unsafe.defineClass 在运行时定义多个类

标签 java instrumentation unsafe verifyerror jvm-bytecode

我正在为我的自定义编程语言开发 REPL。它是在编译器之上实现的,用于生成输入的字节码并将其转换为 Class<?>使用 sun.misc.Unsafe.defineClass(String, byte[], int, int, ClassLoader, ProtectionDomain) 的实例方法。相关代码如下(省略异常处理等无关部分):

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);
}

假设输入需要编译和加载多个类。

> class A { interface B { }; func b() = new B { /* anonymous class */ } }

compilables列表有内容

[ repl.Result_0, repl.Result_0$A, repl.Result_0$A$0, repl.Result_0$A$B ]

repl.Result_0$A类取决于 repl.Result_0$A$0 (匿名)类和 repl.Result_0$B类并在字节码中引用它们的名称。使用 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 定义和加载多个类不会导致未解析的类的验证错误?

最佳答案

您的问题并不特定于Unsafe.defineClass,而是与程序逻辑有关。每当您“推送”多个新类时,无论您是否使用 ClassLoader.defineClassUnsafe.defineClass ,您必须避免前向引用,这会阻止您的类依赖项中出现循环。

对于Unsafe.defineClass的实际预期用例,例如反射访问器,有一个明确的依赖方向,因此没有问题,但对于您的用例来说,它不是正确的工具。您必须定义一个类加载器,允许 JVM 在需要时“拉取”类,例如

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 来像原始代码一样强制执行初始化。通常,代码不应依赖于立即初始化,但您不会将加载的类用于其他任何用途,因此不清楚用什么来替换。通常的过程是使用 loadClass 作为随后要使用的类并返回它;初始化(以及依赖项的加载和初始化)将在其实际使用时发生。

进一步注意,整个代码无需使用 Unsafe...

关于java - 使用 Unsafe.defineClass 在运行时定义多个类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39729671/

相关文章:

java - 如何使用 Netbean IDE 在 Java 库中搜索类?

java - Android JUnit 测试应用类

c# - 使用正确的编码读取固定的字符数组

java - 正确使用 arrayBaseOffset 和 arrayIndexScale

java - 将字符串转换为特定格式的日期,无需时间

java - 词汇学习程序无法正常工作

java 嵌套 for() 循环抛出 ArrayIndexOutOfBoundsException

android - Android API 中的 Instrumentation 类

Java 字节码检测到由 ASPECTJ 识别的方法

c# - 如何将 int 设置为 byte* C#