【问题标题】:Writing Java code that compiles using one of two implementations of a class编写使用类的两个实现之一进行编译的 Java 代码
【发布时间】:2017-02-25 01:11:41
【问题描述】:

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

在 Java 9 中,此类将变得不可访问,并将变为 jdk.unsupported.Unsafe。我想编写我的代码,以便它现在可以工作,但在 Java 9 中继续工作。

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

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

我目前的计划是在运行时加载一个合适的类。

【问题讨论】:

  • 它将是unsupported ...
  • @r1verside 我知道,但几乎可以肯定不会消失,因为很多代码(包括新开发!)都依赖于它。
  • 也许是时候做你很久以前就应该做的事了:修复你的代码(这样你就不会使用任何粗糙的旧 sun 代码)。
  • @Bohemian 如果真的是 any 其他 sun.misc.* 类,我会这样做。但是sun.misc.Unsafe 不是普通的类:它的大多数方法都是无法模拟的内在函数,除非性能损失超过一个数量级。对于我的库(一个 FFI 库),这是不可接受的。 Java 没有提供对原始指针进行操作的标准方法,这不是我的错——这在 C# 中都不是问题。相信我,如果有同样性能的替代品,我会使用它!
  • @demi 可能还有办法。请详细描述您实际想要实现的目标(而不是描述解决方法您想要做什么)

标签: java ffi binary-compatibility source-compatibility


【解决方案1】:

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

如果你可以引入库依赖,cglib 的 FastClass 基本上会为你做到这一点,而且省力很多。

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

【讨论】:

    【解决方案2】:

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

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

    实施开始示例

    /**
     * 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...
    }
    

    【讨论】:

    • 那行不通。每次方法调用的反射开销都会影响性能。我需要一些允许 JIT 内联包装器的东西——根据 JDK 版本加载一个或另一个类。
    • 您确定性能吗?以我的经验,反射并不比 java 进行的通常的内部检查慢多少。
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-25
      • 2012-05-19
      • 2013-09-22
      • 1970-01-01
      • 1970-01-01
      • 2019-07-03
      相关资源
      最近更新 更多