Monday, April 4, 2016

Getting the reference address of a variable on Android

In Java, we've always had our share of undocumented fun through the sun.misc.Unsafe class. You can allocate memory, access data fast without range checks and - what this post is about - get the memory address of some object.

Motivation

So why would you do this in a memory managed language like Java? Well, it's arguably rare you have an actual need, however it can come it handy at times. For instance, I needed it when debugging some weird behavior on Android. I had a strong suspicion that the (insanely complex) Android life-cycle and associated serialization aspects were the cause of me seeing an object instance mutate (I.e. change its hash value). Unfortunately Android Studio doesn't support putting a watch on a variable. What I really needed was a way to document identity equality, just as we can document value equality with the hashCode() mechanism.

Android specifics

It's not hard to find examples of the use of sun.misc.Unsafe on the Internet. Using it on Android rather than the JVM however, means we're operating under some fairly unique constraints. For once thing, you can not import the sun.misc.Unsafe class at all! Secondly, while Android does include an implementation of sun.misc.Unsafe at runtime, it differs substantially from the official unofficial (if you know what I mean) implementation we know from the Oracle JVM. However, by finding the relevant source code at the AOSP, and some reflection magic, we have enough ammo to attack the problem. The end result is the following UnsafeUtils class.

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Utility class for working with the undocumented java.misc.Unsafe class
 * on Android.
 */

public class UnsafeUtils {

    private static Object getInstance(){

        try {
            Class clazz = Class.forName("sun.misc.Unsafe");
            Field theUnsafe = clazz.getDeclaredField("THE_ONE");
            theUnsafe.setAccessible(true);
            return theUnsafe.get(null);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static long addressOf(Object o) {
        final Object[] array = new Object[] {o};

        final Object unsafe = getInstance();
        try {
            final Method arrayBaseOffsetMethod = unsafe.getClass().getMethod("arrayBaseOffset", Class.class);
            int arrayBaseOffset = (int)arrayBaseOffsetMethod.invoke(unsafe, Object[].class);
            final Method getIntMethod = unsafe.getClass().getMethod("getInt", Object.classlong.class);
            return (int)getIntMethod.invoke(unsafe, array, arrayBaseOffset);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }
}


Using UnsafeUtils

Using the utility class is just a matter of getting an instance and calling the addressOf(...) method with the target object as an argument.


        String n = "java";
        System.out.println("n=\"java\": {hashCode=" + n.hashCode() + ",addressOf=" + addressOf(n) + "}");
        n = "rocks";
        System.out.println("n=\"rocks\": {hashCode=" + n.hashCode() + ",addressOf=" + addressOf(n) + "}");
        n = "java";
        System.out.println("n=\"java\": {hashCode=" + n.hashCode() + ",addressOf=" + addressOf(n) + "}");
        n = new String("java");
        System.out.println("'n=new String(\"java\"): {hashCode=" + n.hashCode() + ",addressOf=" + addressOf(n) + "}");


The code above will output something along the line of the following - keep in mind, the memory addresses listed will be different with each run.

n="java": {hashCode=3254818,addressOf=323913376}
n="rocks": {hashCode=108686766,addressOf=323913568}
n="java": {hashCode=3254818,addressOf=323913376}
n=new String("java"): {hashCode=3254818,addressOf=323913856}



Notice how changing our code from referencing a String with "java" to "rocks", makes it point to a new address. Changing it to point to "java" again reuses the existing constant in the pool due to a the Java compiler being smart about it (Strings are somewhat unique). Making a new reference using the "new" operator, bypasses this compiler optimization and allocates a new instance on the heap.

If you play around a bit, you will also start to notice the size of references and you can also easily distinguish the difference between objects created on the stack vs. the heap.

Disclaimer

Take this for what it is, a helper utility for understanding what's going on and for debugging purposes. The code may be buggy, may not work on all versions of Android/Dalvik/ART and is definitely not production safe. Although the code is adapted for my use, it's very unoriginal - so assume a Public Domain license. Do with it what you want, just don't hold me accountable. :)


Post a Comment