You must not know the usage of Unsafe

Keywords: Java jar

What is Unsafe

First of all, we say that the unsafe class is located under the sun.misc package in rt.jar. Unsafe translation is unsafe. This does not mean that this class is unsafe, but that it is unsafe for developers to use unsafe, that is, it is not recommended for developers to use unsafe directly. And there is no unsafe source code in the Oracle JDK source code package. In fact, most of the classes in the JUC package use unsafe. It can be said that unsafe is the cornerstone of java and contracting.

How to get Unsafe objects correctly

We look at how to get Unsafe objects from the source code

private Unsafe() {
}

First, the construction method is privatized, which means that we cannot create Unsafe objects through new Unsafe.

@CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

I also found that there is a static method named getUnsafe and the return value is Unsafe. It seems that calling this method can get the Unsafe object.

To this end, I wrote the following code to test whether it works

public static void main(String[] args) throws Exception{
        Unsafe unsafe = Unsafe.getUnsafe();
        System.out.println(unsafe);
}

Who knows that the console reported a mistake

Looking at the error message is a permission error, but I think the way AtomicBoolean class obtains Unsafe is to call the getUnsafe method, which may only allow classes inside the JDK to access it in this way. We won't go deep here and find another way to obtain it.

Continue to look at the source code to find a breakthrough

The first constant in the Unsafe class is private static final Unsafe theUnsafe; It is decorated with static and final without direct assignment, which means that there must be a static code block assigned to theUnsafe, and then found at the bottom of the class.

static {
        registerNatives();
        Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
        theUnsafe = new Unsafe();
        ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
        ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
        ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class);
        ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);
        ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);
        ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);
        ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class);
        ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class);
        ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class);
        ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);
        ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);
        ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class);
        ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class);
        ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class);
        ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);
        ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class);
        ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class);
        ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
        ADDRESS_SIZE = theUnsafe.addressSize();
    }

The fourth line assigns a value to theUnsafe. In other words, after the class is loaded, the Unsafe constant in Unsafe has been assigned the Unsafe object. If we want to get the Unsafe object, we can just get the Unsafe attribute with reflection.

/**
     * Get Unsafe
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    public static Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        //Private properties can be accessed
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        System.out.println(unsafe);
        return unsafe;
    }

Unsafe implements CAS lock

CAS is the abbreviation of compare and swap, which is translated into Chinese for comparison and exchange. Under the juc package, the classes beginning with Atomic are implemented using CAS lock. Under the concurrency condition, the assignment of a variable is not overwritten. We can also use Unsafe to implement CAS lock ourselves.

 interface Counter{
        void increment();
        long getCounter();
    }
	/**
     * Implement CAS lock with unsafe
     */
    static class CasCounter implements Counter{
        private volatile long counter = 0;
        private Unsafe unsafe;
        private long offset;

        CasCounter() throws NoSuchFieldException, IllegalAccessException {
            unsafe = getUnsafe();
            //Get the starting position of the memory offset of the counter attribute of this class
            offset = unsafe.objectFieldOffset(CasCounter.class.getDeclaredField("counter"));
        }

        @Override
        public void increment() {
            long current = counter;
            //The loop judges whether the assignment is successful. The first parameter is the object calling this method, the second parameter is the memory offset of the attribute to be changed, the third parameter is the value before modification, and the fourth parameter is the value to be modified.
            while (!unsafe.compareAndSwapLong(this,offset,current,current+1)){
                current = counter;
            }
        }

        @Override
        public long getCounter() {
            return counter;
        }
    }

Here, we mainly look at unsafe.objectFieldOffset(CasCounter.class.getDeclaredField("counter"); This line of code, because the attribute value is changed directly in memory through CAS, we need to get the memory offset of the counter attribute of this object.

Take another look at the increment method. Here, we get the value of counter before changing. The unsafe.compareAndSwapLong method changes the value according to the memory offset. The first parameter determines the object, the second parameter determines the attribute, the third parameter compares the original value of the attribute to be changed, and the fourth parameter determines the value to be changed. Returns true if the change is successful and false if the change fails. The logic here is to change all the time when the change fails, and jump out of the loop only after the change is successful. This will effectively prevent the problem of attribute values being overwritten. The CasCounter class we wrote implements some functions of the AtomicInteger class.

Creating objects with Unsafe

We all know that reflection can create objects through the back door. In fact, Unsafe is also possible

static class Simple{
        static {
            System.out.println("Class initialization");
        }
        private long l = 0;

        public Simple(){
            this.l = 1;
            System.out.println("Object initialization");
        }

        public long get(){
            return l;
        }
}

 public static void main(String[] args) throws Exception {
 	    Unsafe unsafe = getUnsafe();
        //It is equivalent to directly opening up an address in memory without running the construction method
        Simple simple = (Simple) unsafe.allocateInstance(Simple.class);
        System.out.println("adopt unsafe Creating objects does not run construction methods: " + simple.get());
        System.out.println("But it can be obtained through objects class object" + simple.getClass());
        System.out.println("You can also get the class loader " + simple.getClass().getClassLoader());
 }

The console output is as follows

Here we find that using Unsafe to create an object does not run the constructor, but just creates the object. Using reflection to create objects will run the construction method, which is the same as using new to create objects. Therefore, it is not recommended to use Unsafe to create objects.

Unsafe load class

Since Unsafe is a direct memory operation, you should also be able to load classes. Let's see how Unsafe loads classes.

Let's write our own class A first

public class A
{
	private int i = 0;
	
	public A(){
		this.i = 10;
	}

	public int get(){
		return i;
	}
}

Then run javac A.java to generate A.class. At this time, the location of A.class is F:\tmp

Secondly, we write the code that Unsafe loads the class

	/**
     * Get binary from class file
     * @return
     * @throws IOException
     */
    public static byte[] loadClassContent() throws IOException {
        File file = new File("F:\\tmp\\a.class");
        FileInputStream fis = new FileInputStream(file);
        byte[] content = new byte[(int) file.length()];
        fis.read(content);
        fis.close();
        return content;
    }
    public static void main(String[] args) throws Exception {
        Unsafe unsafe = getUnsafe();
        byte[] bytes = loadClassContent();
        Class<?> aClass = unsafe.defineClass(null, bytes, 0, bytes.length,null,null);
        Method get = aClass.getMethod("get");
        int i = (int) get.invoke(aClass.newInstance(), null);
        System.out.println(i);
    }

Here, the unsafe.defineClass method is the method to load the class.

After running, the output result is 10

In this way, we can load the class through Unsafe.

Unsafe changing private property values

We all know that reflection can change the private attribute value of an object. In fact, Unsafe can also directly change the private attribute value. The code is as follows

static class Guard{
        private int ACCESS_ALLOWED = 1;

        private boolean allow(){
            return 42==ACCESS_ALLOWED;
        }

        public void work(){
            if (allow()){
                System.out.println("You did a black box operation");
            }
        }
 }
public static void main(String[] args) throws Exception {
        Unsafe unsafe = getUnsafe();
        Guard guard = new Guard();
        Field access_allowed = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
        unsafe.putInt(guard,unsafe.objectFieldOffset(access_allowed),42);
        guard.work();
}

The output result is a black box operation for you. The first parameter of putInt method is the object to which the attribute to be changed belongs, the second parameter is the memory offset of the attribute to be changed, and the third parameter is the value to be changed. In fact, it directly changes the value of the int attribute in the specified memory address. This completes changing the private property value of the object using Unsafe.

The Unsafe class can directly operate memory, which determines that it can go through too many back doors, and most methods are modified by native, and the underlying call is C + +. It is estimated that this is also the reason for Unsafe.

If you like this article, you might as well pay attention to the praise collection. If you have any confusion, please comment.

Welcome down to earth gas programmer's official account.

Posted by Nightslyr on Fri, 29 Oct 2021 01:30:32 -0700