Understanding sun.misc.Unsafe

Foreword

The following sun.misc.Unsafe source code and demo are based on jdk1.7;

Recently, I have been looking at the source code in J.U.C. Many of them use the sun.misc.Unsafe class. I don’t know much about it, but it seems a bit unsatisfactory, so I plan to do an analysis of the source code and usage of Unsafe;

In addition, I found a copy of the C++ source code natUnsafe.cc on the Internet (unfortunately it is relatively old, Copyright (C) 2006, 2007, no new ones were found), which is the C++ implementation of sun.misc.Unsafe, and The native methods in the Unsafe class are easier to understand in comparison;

The role of the Unsafe class

It can be used to read and write data at any memory address location. It can be seen that it is quite dangerous for ordinary users to use;

In addition, some CAS atomic operations are also supported;

Get Unsafe object

Unfortunately, Unsafe objects cannot be obtained directly through new Unsafe() or calling Unsafe.getUnsafe() for the following reasons:

*You cannot directly new Unsafe() because Unsafe is designed as a singleton mode and the construction method is private;

*Cannot be obtained by calling Unsafe.getUnsafe(), because getUnsafe is designed to only be loaded from the bootstrap class loader (bootstrap class loader), from getUnsafe It can also be seen in the source code, as follows:

 @CallerSensitive
    public static Unsafe getUnsafe() {
        //Get the Class object that calls this method
        Class cc = Reflection.getCallerClass();
        //Determine whether the class calling this method is a bootstrap class loader (bootstrap class loader)
        //If not, for example, if this method is called by AppClassLoader, a SecurityException will be thrown.
        if (cc.getClassLoader() != null)
            throw new SecurityException("Unsafe");
        //return singleton object
        return theUnsafe;
    }

Although we cannot get the Unsafe object through the above method, there is a private static global attribute theUnsafe (Unsafe instance object) in the Unsafe class. Through reflection, we can obtain the Field object corresponding to the member attribute theUnsafe. , and set it as accessible to get the Unsafe specific object, as shown in the following code:

package concurrency;

import java.lang.reflect.Field;
import sun.misc.Unsafe;
import sun.reflect.Reflection;

public class Test {
    public static void main(String[] args) throws NoSuchFieldException,
            SecurityException, IllegalArgumentException, IllegalAccessException {
        // Obtain the Field object corresponding to theUnsafe through reflection
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        //Set the Field to be accessible
        field.setAccessible(true);
        // Get the specific object corresponding to the Field through Field. Pass in null because the Field is static.
        Unsafe unsafe = (Unsafe) field.get(null);
        System.out.println(unsafe);

    }
}

API in Unsafe class

allocateInstance method, does not call the constructor to generate objects

Local method, the function is to generate an object instance, but the constructor of the object will not be run; because the natUnsafe.cc version is older, the corresponding C++ implementation was not found;

 /** Allocate an instance but do not run any constructor. Initializes the class if it has not yet been. */
    public native Object allocateInstance(Class cls)
        throws InstantiationException;

Example, using Unsafe’s allocateInstance method to generate an object without calling the constructor:

package concurrency;

import java.lang.reflect.Field;

import sun.misc.Unsafe;
import sun.reflect.Reflection;

class User {
    private String name = "";
    private int age = 0;

    public User() {
        this.name = "test";
        this.age = 22;
    }
    
    @Override
    public String toString() {
        return name + ": " + age;
    }
}


public class Test {
    public static void main(String[] args) throws NoSuchFieldException,
            SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
        // Obtain the Field object corresponding to theUnsafe through reflection
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        //Set the Field to be accessible
        field.setAccessible(true);
        // Get the specific object corresponding to the Field through Field. Pass in null because the Field is static.
        Unsafe unsafe = (Unsafe) field.get(null);

        User user = (User) unsafe.allocateInstance(User.class);
        System.out.println(user); //dont invoke constructor, print null: 0
        
        User userFromNormal = new User();
        System.out.println(userFromNormal); //print test: 22

    }
}

objectFieldOffset method, returns the offset of the member attribute’s address in memory relative to the object’s memory address

Relatively simple, it returns the offset of the member attribute memory address relative to the object’s memory address. This method can calculate the space size of an object in memory by obtaining all its fields through reflection (including those inherited from the parent class). , find the maximum offset value in the Field, and then fill the maximum offset value with the number of bytes that is the object size;

For examples of using this method, see the following example of modifying memory data;

putLong, putInt, putDouble, putChar, putObject and other methods directly modify memory data (access permissions can be exceeded)

Here, there is also the get method corresponding to put, which is very simple to directly read the data at the memory address without giving an example;

We can take the putLong(Object, long, long) method to see its specific implementation in detail. Others are similar. Let’s look at the Java source code first. There is nothing interesting, so we declare a native method:

The three parameters are explained below:

Object o//Object reference
long offset//Offset of object memory address<br>
long x//written data
 public native void putLong(Object o, long offset, long x);

Let’s take a look at the C++ implementation in natUnsafe.cc. It’s very simple, just calculate the memory address where the data is to be written, and then write the data, as follows:

void
sun::misc::Unsafe::putLong (jobject obj, jlong offset, jlong value)
{
  jlong *addr = (jlong *) ((char *) obj + offset);//Calculate the memory address of the data to be modified = object address + member attribute address offset
  spinlock lock;//Spin lock, obtain the lock through loop. The i386 processor needs to lock to access 64-bit data. If it is int, there is no need to change the code.
  *addr = value;//Write data directly to the memory address location
}

In the following example, even if the member properties of the User class are private and do not provide external public methods, we can still write data directly to their memory address locations and succeed;

package concurrency;

import java.lang.reflect.Field;

import sun.misc.Unsafe;
import sun.reflect.Reflection;

class User {
    private String name = "test";
    private long id = 1;
    private int age = 2;
    private double height = 1.72;
    

    @Override
    public String toString() {
        return name + "," + id + "," + age + "," + height;
    }
}


public class Test {
    public static void main(String[] args) throws NoSuchFieldException,
            SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
        // Obtain the Field object corresponding to theUnsafe through reflection
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        //Set the Field to be accessible
        field.setAccessible(true);
        // Get the specific object corresponding to the Field through Field. Pass in null because the Field is static.
        Unsafe unsafe = (Unsafe) field.get(null);

        User user = new User();
        System.out.println(user); //Print test,1,2,1.72
        
        Class userClass = user.getClass();
        Field name = userClass.getDeclaredField("name");
        Field id = userClass.getDeclaredField("id");
        Field age = userClass.getDeclaredField("age");
        Field height = userClass.getDeclaredField("height");
        //Write data directly to the memory address
        unsafe.putObject(user, unsafe.objectFieldOffset(name), "midified-name");
        unsafe.putLong(user, unsafe.objectFieldOffset(id),100l);
        unsafe.putInt(user, unsafe.objectFieldOffset(age), 101);
        unsafe.putDouble(user, unsafe.objectFieldOffset(height), 100.1);
        
        System.out.println(user);//Print midified-name,100,101,100.1

    }
}

copyMemory, freeMemory

copyMemory: memory data copy

freeMemory: used to release the memory requested by allocateMemory and reallocateMemory

CAS operation methods, compareAndSwapInt, compareAndSwapLong, etc.

Let’s take a look at the C++ implementation in natUnsafe.cc to deepen our understanding. In fact, it is to compare the memory value with the expected value to determine whether they are equal. If they are equal, write the data. If they are not equal, no operation will be performed and the old data will be returned;

static inline bool
compareAndSwap (volatile jint *addr, jint old, jint new_val)
{
  jboolean result = false;
  spinlock lock;
  if ((result = (*addr == old)))
    *addr = new_val;
  return result;
}

The atomic class in J.U.C is implemented based on the above CAS operations;

getLongVolatile/putLongVolatile and other methods

This type of method uses volatile semantics to access data. My understanding is that each thread does not cache data and reads data directly from memory;

Reference connection:

https://github.com/aeste/gcc/blob/master/libjava/sun/misc/natUnsafe.cc

http://ifeve.com/sun-misc-unsafe/

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/misc/Unsafe.java