6

I am writing some FFI code in Java that makes heavy use of sun.misc.Unsafe.

In Java 9, this class will become inaccessible, and will become jdk.unsupported.Unsafe. I would like to write my code so that it works now, but continues to work in Java 9.

What is the least hacky way to do this? I would prefer binary compatibility, but source compatibility is also okay.

Edit: I am 100% not okay with using reflection – or even virtual dispatch – every time a method on Unsafe is called. Most of those methods compile to a single machine instruction. Therefore, performance really matters. It's okay to have wrappers – but only if I can be sure the JIT will inline them, every time.

My current plan is to load an appropriate class at runtime.

linuxbuild
  • 15,843
  • 6
  • 60
  • 87
Demi
  • 3,535
  • 5
  • 29
  • 45
  • It will be `unsupported` ... – Pablo Recalde Oct 15 '16 at 18:48
  • @r1verside I know, but it is almost certain not to go away, because so much code (including new development!) depends on it. – Demi Oct 15 '16 at 18:50
  • Maybe it's time to do what you should have done a long time ago: Fix your code (so you don't use any gnarly old sun code). – Bohemian Oct 15 '16 at 23:23
  • 1
    @Bohemian If it were literally _any_ other `sun.misc.*` class I would do just that. But `sun.misc.Unsafe` is no ordinary class: most of its methods are intrinsics that cannot be emulated except at a performance penalty of over an order of magnitude. For my library (an FFI library) that is unacceptable. It's not my fault that Java provides no standard way of operating on raw pointers – none of this would be an issue in C#. Trust me, if there was an equally-performant alternative I would use it! – Demi Oct 16 '16 at 04:08
  • 1
    @demi there may yet be a way. Please describe in detail exactly what you *actually* want to achieve (rather than describe what *work around* you want to do) – Bohemian Oct 16 '16 at 05:01
  • @Bohemian I want to use `sun.misc.Unsafe` on JDK <= 8, and `jdk.unsupported.Unsafe` on JDK 9, so that I can perform high-speed operations on raw pointers. These classes are hard-coded into the JVM – the "magic" that lets them do what they do so fast is not available to other classes. – Demi Oct 18 '16 at 03:10

3 Answers3

2

One option: You could have a small shim helper interface for the method(s) on Unsafe that you need access to, with two implementations: one for sun.misc.Unsafe and one for the new jdk.unsupported.Unsafe. Both classes could be stored as binary resources in your JAR, and during class initialization (that is, in a static block) you could create a new ClassLoader and load the shim class. This would give you some reflective overhead during class initialization, but at runtime there's no reflection involved, only a virtual method dispatch -- which the JIT should be able to inline, so long as you only load the one implementation class.

If you can introduce a library dependency, cglib's FastClass will basically do this for you with a lot less effort.

As always with low-level performance hacks like this, you need some data. Create a JMH test harness for this and verify that the reflection overhead really is intolerable, and that the JIT really can inline your solution -- that's the only way to be certain.

Daniel Pryden
  • 59,486
  • 16
  • 97
  • 135
0

One way would be to write a class that wraps either implementation and uses a helper method to get the right implementation. You could check System.getProperties().getProperty("java.version") to know if you are in JDK 1.8 or 1.9 or you can just try/catch to get the class like I did.

Unfortunately there is no Interface to implement so you'll have to use it like a plain Object. You'll have to wrap every method. Maybe there is a more generic way to do this.

Example start of implementation

/**
 * 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...
}
pamcevoy
  • 1,136
  • 11
  • 15
  • 1
    That won't work. The reflection overhead on each method invocation will kill performance. I need something that allows the JIT to inline the wrappers – something along the lines of loading one class or another depending upon the JDK version. – Demi Oct 15 '16 at 22:24
  • Are you sure about performance? In my experience reflection isn't that much slower than the usual internal checking done by java. – pamcevoy Oct 16 '16 at 10:04
0

The low-tech solution, of course, would be to simply document that your library is incompatible with Java 9, and release a separate version branch that works on Java 9 (but not on previous versions). This pushes the problem out to your customers, but that's likely to be good enough for the vast majority of real-world use cases.

Daniel Pryden
  • 59,486
  • 16
  • 97
  • 135