编写 Java 代码,使用 class 的两个实现之一进行编译

Writing Java code that compiles using one of two implementations of a class

我正在 Java 中编写一些 FFI 代码,大量使用 sun.misc.Unsafe

在Java9中,这个class将变得不可访问,并且会变成jdk.unsupported.Unsafe。我想编写我的代码以便它现在可以工作,但在 Java 9.

中继续工作

最简单的方法是什么?我更喜欢二进制兼容性,但源兼容性也可以。

编辑:每次调用 Unsafe 上的方法时,我 100% 不同意使用反射——甚至是虚拟分派。这些方法中的大多数 编译为单个机器指令 。因此,性能真的很重要。可以使用包装器 – 但前提是我可以确定 JIT 每次都会内联它们。

我目前的计划是在运行时加载适当的 class。

一种方法是编写一个 class 来包装任一实现并使用辅助方法来获得正确的实现。您可以检查 System.getProperties().getProperty("java.version") 以了解您使用的是 JDK 1.8 还是 1.9,或者您可以 try/catch 获取 class就像我一样。

遗憾的是,没有可实现的接口,因此您必须像普通对象一样使用它。您必须包装每个方法。也许有更通用的方法来做到这一点。

示例开始实施

/**
 * A wrapper to a sun.misc.Unsafe or jdk.unsupported.Unsafe object.
 */
public class MyUnsafe {

    // the implementing class (sun.misc.Unsafe or jdk.unsupported.Unsafe)
    private Class<?> unsafeCls;

    // an instance of the implementing class
    private Object unsafeObj;

    // constructor
    public MyUnsafe() throws InstantiationException, IllegalAccessException {
        unsafeCls = getImplClass();
        unsafeObj = unsafeCls.newInstance();
    }

    // get the implementing class
    private Class<?> getImplClass() {
        Class<?> implClass = null;
        try {
            // JDK 1.8 and earlier
            implClass = Class.forName("sun.misc.Unsafe");
        }
        catch (ClassNotFoundException e1) {
            try {
                // JDK 1.9 and later
                implClass = Class.forName("jdk.unsupported.Unsafe");
            }
            catch (ClassNotFoundException e2) {
                // TODO - something that wraps both e1 and e2
                throw new RuntimeException(e2);
            }
        }
        return implClass;
    }

    // Wrap methods

    // for example
    public Object getObject(Object obj, long offset) throws Exception {
        Method mtd = unsafeCls.getMethod("getObject", new Class<?>[] { Object.class, long.class });
        return mtd.invoke(unsafeObj, new Object[] { obj, offset });
    }

    // and so on...
}

一个选项:您可以为需要访问的 Unsafe 上的方法提供一个小的 shim 帮助程序接口,有两种实现:一种用于 sun.misc.Unsafe,一种用于新的 jdk.unsupported.Unsafe.两个 classes 都可以作为二进制资源存储在您的 JAR 中,并且在 class 初始化期间(即在 static 块中)您可以创建一个新的 ClassLoader 并加载 shim class。这会在 class 初始化期间给你一些反射开销,但在运行时不涉及反射,只有一个虚拟方法分派——JIT 应该能够内联,只要你只加载一个实现 class.

如果你可以引入一个库依赖,cglib 的 FastClass 基本上会为你做这件事,而不需要很多努力。

像这样的低级性能黑客一样,您需要一些数据。为此创建一个 JMH 测试工具,并验证反射开销确实 无法忍受的,并且 JIT 确实可以内联您的解决方案——这是唯一可以确定的方法。

当然,低技术含量的解决方案是简单地记录您的库与 Java 9 不兼容,并发布一个适用于 Java 9 的单独版本分支(但不是在以前的版本上)。这会将问题推给您的客户,但这对于绝大多数实际用例来说可能已经足够好了。