Java Unsafe Learning Notes Sharing

  • 2021-12-12 04:14:09
  • OfStack

Directory sun. misc. Unsafe Get Unsafe Instance Focus API Use Scenario Avoid Initialization Memory Crash (Memory corruption) Throw Exception (Throw an Exception) Large Array (Big Arrays) Concurrent (Concurrency) Suspend and Restore Unsafe API Knowledge Point park and unpark Flexibility

sun.misc.Unsafe

Function: It can be used to read and write data at any memory address location, and support 1 CAS atomic operation

Java was originally designed as a secure controlled environment. Nevertheless, HotSpot includes a backdoor sun. misc. Unsafe, which provides a number of low-level operations that can directly control memory and threads. Unsafe is widely used by JDK in java. nio and parallel contracting implementations. This insecure class provides a way to observe and modify the internal structure of HotSpot and JVM, but is not recommended for use in production environments

Get an Unsafe instance

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

You cannot directly new Unsafe () because Unsafe is designed as a singleton pattern and the construction method is private; Cannot be obtained by calling Unsafe. getUnsafe () because getUnsafe is designed to load only from the boot class loader (bootstrap class loader)

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

Get an instance


// Method 1 We can make our code "trusted". When running a program, use the bootclasspath Option, specifying the system classpath plus the 1 A Unsafe Path 
java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient
//  Method 2
static {
    try {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        UNSAFE = (Unsafe) field.get(null);
    } catch (Exception e) {
    }
}

Note: Ignore your IDE. For example: eclipse displays an "Access restriction …" error, but if you run the code, it will work normally. If this error is annoying, you can avoid it by setting the following:

Preferences - > Java - > Compiler - > Errors/Warnings - > Deprecated and restricted API - > Forbidden reference - > Warning

Focus API

allocateInstance(Class < ? > var1) Build objects without calling constructors

User instance = (User) UNSAFE.allocateInstance(User.class);
objectFieldOffset (Field var1) returns the offset of the in-memory address of a member property relative to the object memory address putLong, putInt, putDouble, putChar, putObject and other methods, directly modify memory data (can bypass access rights)

package com.quancheng;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class CollectionApp {
    private static sun.misc.Unsafe UNSAFE;
    public static void main(String[] args) {
        try {
            User instance = (User) UNSAFE.allocateInstance(User.class);
            instance.setName("luoyoub");
            System.err.println("instance:" + instance);
            instance.test();
            Field name = instance.getClass().getDeclaredField("name");
            UNSAFE.putObject(instance, UNSAFE.objectFieldOffset(name), "huanghui");
            instance.test();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            UNSAFE = (Unsafe) field.get(null);
        } catch (Exception e) {
        }
    }
}
class User {
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    public void test() {
        System.err.println("hello,world" + name);
    }
}
copyMemory Memory data copy freeMemory Used to free memory requested by allocateMemory and reallocateMemory compareAndSwapInt / compareAndSwapLongCAS Operation getLongVolatile / putLongVolatile

Usage scenario

Avoid initialization

The allocateInstance method is very useful when you want to skip the object initialization phase, or bypass the security check of the constructor, or instantiate a class without any common constructor. Initializing it with constructor, reflection, and unsafe will get different results


public class CollectionApp {
    private static sun.misc.Unsafe UNSAFE;
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        A a = new A();
        a.test(); // output ==> 1
        A a1 = A.class.newInstance();
        a1.test(); // output ==> 1
        A instance = (A) UNSAFE.allocateInstance(A.class);
        instance.test(); // output ==> 0
    }
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            UNSAFE = (Unsafe) field.get(null);
        } catch (Exception e) {
        }
    }
}
class A{
    private long a;
    public A(){
        a = 1;
    }
    public void test(){
        System.err.println("a==>" + a);
    }
}

Memory corruption (Memory corruption)

Unsafe can be used to bypass common security techniques and directly modify memory variables; In fact, reflection can achieve the same function. However, it is worth noting that we can modify any object, even without references to these objects


Guard guard = new Guard();
guard.giveAccess();   // false, no access
// bypass
Unsafe unsafe = getUnsafe();
Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruption
guard.giveAccess(); // true, access granted

Note: We don't have to hold a reference to this object

Shallow copy (Shallow copy) Dynamic Class (Dynamic classes)

We can create a class at run time, such as from a compiled. class file. Read the contents of the class as a byte array and pass it correctly to the defineClass method; This is useful when you have to create a class dynamically and there are 1 proxy in the existing code


private static byte[] getClassContent() throws Exception {
    File f = new File("/home/mishadoff/tmp/A.class");
    FileInputStream input = new FileInputStream(f);
    byte[] content = new byte[(int)f.length()];
    input.read(content);
    input.close();
    return content;
}
byte[] classContents = getClassContent();
Class c = getUnsafe().defineClass(
              null, classContents, 0, classContents.length);
c.getMethod("a").invoke(c.newInstance(), null); // 1

Throw an exception (Throw an Exception)

This method throws the checked exception, but your code doesn't have to catch or re-throw it, just like runtime exception 1


getUnsafe().throwException(new IOException());

Large Array (Big Arrays)

As you know, the maximum size of the Java array is Integer. MAX_VALUE. With direct memory allocation, the array size we create is limited to the heap size; In fact, this is out-of-heap memory (off-heap memory) technology, which is partially available in the java. nio package;

Memory allocation in this way is not on the heap and is not managed by GC, so care must be taken with Unsafe. freeMemory (). It also does not perform any boundary checking, so any illegal access may cause JVM to crash


class SuperArray {
    private final static int BYTE = 1;
    private long size;
    private long address;
    public SuperArray(long size) {
        this.size = size;
        address = getUnsafe().allocateMemory(size * BYTE);
    }
    public void set(long i, byte value) {
        getUnsafe().putByte(address + i * BYTE, value);
    }
    public int get(long idx) {
        return getUnsafe().getByte(address + idx * BYTE);
    }
    public long size() {
        return size;
    }
}
long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;
SuperArray array = new SuperArray(SUPER_SIZE);
System.out.println("Array size:" + array.size()); // 4294967294
for (int i = 0; i < 100; i++) {
    array.set((long)Integer.MAX_VALUE + i, (byte)3);
    sum += array.get((long)Integer.MAX_VALUE + i);
}
System.out.println("Sum of 100 elements:" + sum);  // 300

Concurrent (Concurrency)

A few words about the concurrency of Unsafe. The compareAndSwap method is atomic and can be used to implement high performance, lock-free data structures

Suspend and resume

Definition:


public native void unpark(Thread jthread);  
public native void park(boolean isAbsolute, long time); // isAbsolute Parameter indicates whether the time is absolute or relative 

Suspending a thread is realized by park method. After calling park, the thread blocks 1 straight until conditions such as timeout or interruption appear. unpark can terminate 1 suspended thread and bring it back to normal. Suspending operations on threads in the whole concurrency framework are encapsulated in the LockSupport class. There are various versions of pack methods in the LockSupport class, but they all call Unsafe. park () method in the end;

The unpark function provides a "permission (permit)" to the thread, and the thread calls the park function and waits for the "permission". This is a bit like a semaphore, but this "permission" cannot be superimposed, and the "permission" is quadratic; For example, the thread B calls the unpark function three times in succession, and when the thread A calls the park function, this "permission" is used. If the thread A calls park again, it enters the waiting state, as shown in the following example Example1


// Method 1 We can make our code "trusted". When running a program, use the bootclasspath Option, specifying the system classpath plus the 1 A Unsafe Path 
java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient
//  Method 2
static {
    try {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        UNSAFE = (Unsafe) field.get(null);
    } catch (Exception e) {
    }
}
0

Note that the unpark function can be called before park. For example, when thread B calls unpark function and issues a "license" to thread A, when thread A calls park, it finds that there is already a "license", so it will continue running immediately. In fact, the park function sometimes returns for no reason even without "permission". In fact, in SUN Jdk, object. wait () may also be falsely woken up;

Note: The unpark method is best not to call unpark on the current thread before calling park

Unsafe API


// Method 1 We can make our code "trusted". When running a program, use the bootclasspath Option, specifying the system classpath plus the 1 A Unsafe Path 
java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient
//  Method 2
static {
    try {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        UNSAFE = (Unsafe) field.get(null);
    } catch (Exception e) {
    }
}
1

Knowledge point

Unsafe. park () returns directly when it encounters a thread termination (unlike Thread. sleep, Thread. sleep throws an exception when it encounters thread. interrupt ())


// Method 1 We can make our code "trusted". When running a program, use the bootclasspath Option, specifying the system classpath plus the 1 A Unsafe Path 
java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient
//  Method 2
static {
    try {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        UNSAFE = (Unsafe) field.get(null);
    } catch (Exception e) {
    }
}
2

unpark cannot recover threads in sleep and can only be paired with park because only park can listen to the license issued by unpark


public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        try {
            System.err.println("sub thread start");
            TimeUnit.SECONDS.sleep(10);
            System.err.println("sub thread end");
        } catch (Exception e) {
            e.printStackTrace();
        }
    });
    thread.start();
    TimeUnit.SECONDS.sleep(3);
    UNSAFE.unpark(thread);
    System.out.println("SUCCESS!!!");
}

Flexibility of park and unpark

As mentioned above, unpark functions can be called before park, which is their flexibility.

One thread may call park before, after or at the same time as other threads unPark, so because of the characteristics of park, it doesn't have to worry about the timing problem of its own park, otherwise, if park must be before unpark, it will bring great trouble to programming! !

"Consider 1. Two threads are synchronized. What should I do?

In Java5, it is synchronized with wait/notify/notifyAll. One big pain with the wait/notify mechanism is that, for example, if the thread B notifies the thread A with notify, then the thread B should ensure that the thread A has already waited on the wait call, otherwise the thread A may always wait. It hurts when programming.

In addition, is notify called or notifyAll called?

notify only wakes up one thread, and if two threads are mistakenly waiting for wait on the same object, it would be tragic again. For security reasons, it seems that only notifyAll can be called. "

The park/unpark model truly decouples the synchronization between threads, and threads no longer need an Object or other variables to store state, and no longer need to care about each other's state


Related articles: