Is it possible to pin an Object using JNI global references

I really need to do some crazy hackery, but it requires certain objects not being moved around the heap. I was thinking… could these global references be used?

http://java.sun.com/docs/books/jni/html/refs.html
“5.1.2 Global References” (Scroll to about 30% of the page)

Anybody experience? Anybody with a C compiler setup to actually work (mine is teasing me) that could copy and paste this?

To determine whether the object moved, thus to track the pointer of an Object, you can use this hackery code:


import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import sun.misc.Unsafe;

public class OneOfTheNativeHacks
{
   private final static Unsafe   unsafe;
   private static final long     BITS;
   private static final long     OFF;
   private static final Object[] holder = new Object[1];

   static
   {
      try
      {
         ByteBuffer buffer = ByteBuffer.allocateDirect(1);
         Field unsafeField = buffer.getClass().getDeclaredField("unsafe");
         unsafeField.setAccessible(true);
         unsafe = (Unsafe) unsafeField.get(buffer);
         unsafeField.setAccessible(false);

         buffer.flip();
      }
      catch (Exception exc)
      {
         exc.printStackTrace();
         throw new InternalError();
      }

      BITS = unsafe.addressSize() * 8;
      OFF = unsafe.arrayBaseOffset(holder.getClass());
   }

   public static final synchronized long getObjectAddress(Object obj)
   {
      holder[0] = obj;
      if (BITS == 32)
         return unsafe.getInt(holder, OFF);
      if (BITS == 64)
         return unsafe.getLong(holder, OFF);
      throw new IllegalStateException();
   }
}

Sounds like awful hackery. What naughtiness are you up to?

Cas :slight_smile:

Structs.

They mustn’t move.

Ow, very naughty. What if they get stuck in eden?

Cas :slight_smile:

Nah, I’m faking objects. I’m writing them (the object headers) at some allocated memory location, and they shouldn’t move into the heap.

For example: I have a float[] and a FloatBuffer that point to the same memory location. It is the ultimate solution to the performance problems with FloatBuffers, while still being able to send them to the GPU without copying the darn thing. It all works very nicely, until the GC kicks in and moves my float[] into the heap.

Now if anybody could give the code a whirl (on the mentioned URL) I’d be grateful!

It would even be enough to force the GC not to run, but from what I understand, that’s impossible, unless you’re not calling ‘new’… anywhere, maybe? Hard to guarantee that if my code would be in a 3rd party lib and somebody else would use it, as “x=”+x; would suddenly potentially separate your float[] from your FloatBuffer.

The code in the page you link to creates a reference to String.class. What kind of tests do you expect to perform on it?

Here’s a little demo, but I don’t think it proves anything useful.


pjt33@agape:/tmp/jni/$ cat testPin.c
#include <jni.h>

JNIEXPORT jclass JNICALL Java_TestPin_pinStringClass(JNIEnv * env, jclass clazz)
 {
     static jclass stringClass = NULL;

     if (stringClass == NULL) {
         jclass localRefCls =
             (*env)->FindClass(env, "java/lang/String");
         if (localRefCls == NULL) {
             return NULL; /* exception thrown */
         }
         /* Create a global reference */
         stringClass = (*env)->NewGlobalRef(env, localRefCls);

         /* The local reference is no longer useful */
         (*env)->DeleteLocalRef(env, localRefCls);

         /* Is the global reference created successfully? */
         if (stringClass == NULL) {
             return NULL; /* out of memory exception thrown */
         }
     }

pjt33@agape:/tmp/jni/$ gcc -o libtestPin.so -shared -I/usr/lib/jvm/jdk/include -I/usr/lib/jvm/jdk/include/linux testPin.c -static
pjt33@agape:/tmp/jni/$ cat TestPin.java
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import sun.misc.Unsafe;

public class TestPin
{
        public static native Class pinStringClass();
        static
        {
                System.loadLibrary("testPin");
        }

        public static void main(String[] args) throws Exception
        {
                Class foo = pinStringClass();
                System.out.println(getObjectAddress(foo));
                // Churn to force some GC.
                ArrayList<String> list = new ArrayList<String>();
                for (int i = 0; i < 1000000; i++)
                    list.add((String)foo.newInstance());
                System.out.println(getObjectAddress(foo));
        }

   private final static Unsafe   unsafe;
   private static final long     BITS;
   private static final long     OFF;
   private static final Object[] holder = new Object[1];

   static
   {
      try
      {
         ByteBuffer buffer = ByteBuffer.allocateDirect(1);
         Field unsafeField = buffer.getClass().getDeclaredField("unsafe");
         unsafeField.setAccessible(true);
         unsafe = (Unsafe) unsafeField.get(buffer);
         unsafeField.setAccessible(false);

         buffer.flip();
      }
      catch (Exception exc)
      {
         exc.printStackTrace();
         throw new InternalError();
      }

      BITS = unsafe.addressSize() * 8;
      OFF = unsafe.arrayBaseOffset(holder.getClass());
   }

   public static final synchronized long getObjectAddress(Object obj)
   {
      holder[0] = obj;
      if (BITS == 32)
         return unsafe.getInt(holder, OFF);
      if (BITS == 64)
         return unsafe.getLong(holder, OFF);
      throw new IllegalStateException();
   }

}
pjt33@agape:/tmp/jni/$ javac TestPin.java
TestPin.java:4: warning: sun.misc.Unsafe is Sun proprietary API and may be removed in a future release
import sun.misc.Unsafe;
               ^
TestPin.java:25: warning: sun.misc.Unsafe is Sun proprietary API and may be removed in a future release
   private final static Unsafe   unsafe;
                        ^
TestPin.java:37: warning: sun.misc.Unsafe is Sun proprietary API and may be removed in a future release
         unsafe = (Unsafe) unsafeField.get(buffer);
                   ^
3 warnings
pjt33@agape:/tmp/jni/$ LD_LIBRARY_PATH=. java -verbose:gc TestPin
-1321504640
[GC 896K->664K(5056K), 0.0059970 secs]
[GC 1560K->1456K(5056K), 0.0055870 secs]
[GC 2352K->2351K(5056K), 0.0064100 secs]
[GC 3247K->3247K(5056K), 0.0052960 secs]
[GC 4143K->4141K(5056K), 0.0060390 secs]
[GC 5037K->5036K(5960K), 0.0048390 secs]
[Full GC 5036K->4462K(5960K), 0.0485570 secs]
[GC 5358K->5356K(8400K), 0.0053860 secs]
[GC 6024K->6015K(8400K), 0.0046480 secs]
[GC 6911K->6911K(8400K), 0.0040350 secs]
[GC 7807K->7805K(8784K), 0.0063760 secs]
[Full GC 7805K->7288K(8784K), 0.0641510 secs]
[GC 8184K->8182K(13108K), 0.0054620 secs]
[GC 10241K->10240K(13108K), 0.0070920 secs]
[GC 11136K->11135K(13108K), 0.0062270 secs]
[GC 12031K->12029K(13108K), 0.0061340 secs]
[GC 12925K->12924K(13876K), 0.0064870 secs]
[Full GC 12924K->12149K(13876K), 0.0930100 secs]
[GC 15366K->15365K(21852K), 0.0107320 secs]
[GC 16837K->16836K(21852K), 0.0101650 secs]
[GC 18308K->18306K(21852K), 0.0291330 secs]
[GC 19778K->19778K(21852K), 0.0105530 secs]
[GC 20703K->20689K(22236K), 0.0078490 secs]
[Full GC 20689K->19304K(22236K), 0.1392640 secs]
[GC 24098K->24097K(34608K), 0.0165440 secs]
[GC 26273K->26272K(34608K), 0.0168730 secs]
[GC 28448K->28447K(34608K), 0.0402030 secs]
[GC 30623K->30622K(34608K), 0.0166760 secs]
[GC 30667K->30648K(34608K), 0.0016900 secs]
[Full GC 30648K->28903K(34608K), 0.1865680 secs]
[GC 36094K->36093K(51824K), 0.0243340 secs]
[GC 39357K->39355K(51824K), 0.0254730 secs]
[GC 42619K->42618K(51824K), 0.0263010 secs]
-1321504640
pjt33@agape:/tmp/jni/$

Yeah well, sorry, not a copy-and-paste, just storing a pointer to a passed object in a global ref, just like the String.class is stored.

Sorry.

Can try it. Hang on…

Edit: actually, forget it. I just ran the same test without calling the native code, and the “object address” survived multiple full GCs unchanged, so you need a better methodology for checking whether the address is pinned or not. (I’m assuming that objects are created in Eden and don’t stay there through a full GC - unless there have been some major redesigns to the garbage collector I think that’s a safe assumption).

Edit2: and the following:


JNIEXPORT jobject JNICALL Java_TestPin_pinStringClass(JNIEnv * env, jclass clazz, jobject foo)
 {
        return (*env)->NewGlobalRef(env, foo);
 }

applied to an object doesn’t change the “object address”.