Guide to sun.misc.Unsafesun.misc.unsafe指南

最后修改: 2017年 4月 18日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we’ll have a look at a fascinating class provided by the JRE – Unsafe from the sun.misc package. This class provides us with low-level mechanisms that were designed to be used only by the core Java library and not by standard users.

在这篇文章中,我们将看看JRE提供的一个迷人的类–Unsafe,来自sun.misc包。这个类为我们提供了低级别的机制,这些机制被设计为仅由核心Java库使用,而不是由标准用户使用。

This provides us with low-level mechanisms primarily designed for internal use within the core libraries.

这为我们提供了主要为核心库内部使用而设计的低层次机制。

2. Obtaining an Instance of the Unsafe

2.获得一个不安全的实例

Firstly, to be able to use the Unsafe class, we need to get an instance – which is not straightforward given the class was designed only for the internal usage.

首先,为了能够使用Unsafe类,我们需要获得一个实例–鉴于该类只为内部使用而设计,这并不简单。

The way to obtain the instance is via the static method getUnsafe(). The caveat is that by default – this will throw a SecurityException.

获得该实例的方法是通过静态方法getUnsafe()。需要注意的是,默认情况下–这将抛出一个SecurityException

Fortunately, we can obtain the instance using reflection:

幸运的是,我们可以使用反射获得实例:

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);

3. Instantiating a Class Using Unsafe

3.使用Unsafe实例化一个类

Let’s say that we have a simple class with a constructor that sets a variable value when the object is created:

假设我们有一个简单的类,它的构造函数在对象被创建时设置了一个变量值。

class InitializationOrdering {
    private long a;

    public InitializationOrdering() {
        this.a = 1;
    }

    public long getA() {
        return this.a;
    }
}

When we initialize that object using the constructor, the getA() method will return a value of 1:

当我们使用构造函数初始化该对象时,getA()方法将返回一个1的值。

InitializationOrdering o1 = new InitializationOrdering();
assertEquals(o1.getA(), 1);

But we can use the allocateInstance() method using Unsafe. It will only allocate the memory for our class, and will not invoke a constructor:

但是我们可以使用allocateInstance() 方法,使用Unsafe.它将只为我们的类分配内存,而不会调用构造函数。

InitializationOrdering o3 
  = (InitializationOrdering) unsafe.allocateInstance(InitializationOrdering.class);
 
assertEquals(o3.getA(), 0);

Notice the constructor was not invoked and due to that fact, the getA() method returned the default value for the long type – which is 0.

注意到构造函数没有被调用,由于这个事实,getA()方法返回long类型的默认值–即0。

4. Altering Private Fields

4.修改私人字段

Let’s say that we have a class that holds a secret private value:

比方说,我们有一个持有secret私有值的类。

class SecretHolder {
    private int SECRET_VALUE = 0;

    public boolean secretIsDisclosed() {
        return SECRET_VALUE == 1;
    }
}

Using the putInt() method from Unsafe, we can change a value of the private SECRET_VALUE field, changing/corrupting the state of that instance:

使用putInt() 方法从Unsafe,我们可以改变私有SECRET_VALUE 字段的一个值,改变/破坏该实例的状态。

SecretHolder secretHolder = new SecretHolder();

Field f = secretHolder.getClass().getDeclaredField("SECRET_VALUE");
unsafe.putInt(secretHolder, unsafe.objectFieldOffset(f), 1);

assertTrue(secretHolder.secretIsDisclosed());

Once we get a field by the reflection call, we can alter its value to any other int value using the Unsafe.

一旦我们通过反射调用得到一个字段,我们就可以使用Unsafe将其值改为任何其他int值。

5. Throwing an Exception

5.抛出一个异常

The code that is invoked via Unsafe is not examined in the same way by the compiler as regular Java code. We can use the throwException() method to throw any exception without restricting the caller to handle that exception, even if it’s a checked exception:

通过Unsafe调用的代码不会像普通的Java代码那样被编译器检查。我们可以使用throwException()方法来抛出任何异常,而不限制调用者处理该异常,即使它是一个被检查的异常。

@Test(expected = IOException.class)
public void givenUnsafeThrowException_whenThrowCheckedException_thenNotNeedToCatchIt() {
    unsafe.throwException(new IOException());
}

After throwing an IOException, which is checked, we don’t need to catch it nor specify it in the method declaration.

在抛出一个IOException,被检查后,我们不需要捕捉它,也不需要在方法声明中指定它。

6. Off-Heap Memory

6.非堆栈式内存

If an application is running out of available memory on the JVM, we could end up forcing the GC process to run too often. Ideally, we would want a special memory region, off-heap and not controlled by the GC process.

如果一个应用程序正在耗尽JVM上的可用内存,我们最终会迫使GC进程运行得太频繁。理想情况下,我们希望有一个特殊的内存区域,在堆外,不受GC进程的控制。

The allocateMemory() method from the Unsafe class gives us the ability to allocate huge objects off the heap, meaning that this memory will not be seen and taken into account by the GC and the JVM.

来自Unsafe类的allocateMemory()方法给了我们从堆外分配巨大对象的能力,这意味着这些内存将不会被GC和JVM看到和考虑到

This can be very useful, but we need to remember that this memory needs to be managed manually and properly reclaiming with freeMemory() when no longer needed.

这可能非常有用,但我们需要记住,这些内存需要手动管理,并在不再需要时用freeMemory()正确回收。

Let’s say that we want to create the large off-heap memory array of bytes. We can use the allocateMemory() method to achieve that:

比方说,我们想创建大的堆外内存字节数。我们可以使用allocateMemory()方法来实现这一目标。

class OffHeapArray {
    private final static int BYTE = 1;
    private long size;
    private long address;

    public OffHeapArray(long size) throws NoSuchFieldException, IllegalAccessException {
        this.size = size;
        address = getUnsafe().allocateMemory(size * BYTE);
    }

    private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        return (Unsafe) f.get(null);
    }

    public void set(long i, byte value) throws NoSuchFieldException, IllegalAccessException {
        getUnsafe().putByte(address + i * BYTE, value);
    }

    public int get(long idx) throws NoSuchFieldException, IllegalAccessException {
        return getUnsafe().getByte(address + idx * BYTE);
    }

    public long size() {
        return size;
    }
    
    public void freeMemory() throws NoSuchFieldException, IllegalAccessException {
        getUnsafe().freeMemory(address);
    }
}

In the constructor of the OffHeapArray, we’re initializing the array that is of a given size. We are storing the beginning address of the array in the address field. The set() method is taking the index and the given value that will be stored in the array. The get() method is retrieving the byte value using its index that is an offset from the start address of the array.

OffHeapArray的构造函数中,我们正在初始化具有指定大小的数组。我们将数组的起始地址存储在address字段中。set()方法正在获取索引和给定的,该值将被存储在数组中。get()方法使用其索引检索字节值,该索引是从数组的起始地址偏移过来的。

Next, we can allocate that off-heap array using its constructor:

接下来,我们可以使用其构造函数来分配这个离堆数组。

long SUPER_SIZE = (long) Integer.MAX_VALUE * 2;
OffHeapArray array = new OffHeapArray(SUPER_SIZE);

We can put N numbers of byte values into this array and then retrieve those values, summing them up to test if our addressing works correctly:

我们可以将N个字节的值放入这个数组,然后检索这些值,将它们相加以测试我们的寻址是否正确。

int sum = 0;
for (int i = 0; i < 100; i++) {
    array.set((long) Integer.MAX_VALUE + i, (byte) 3);
    sum += array.get((long) Integer.MAX_VALUE + i);
}

assertEquals(array.size(), SUPER_SIZE);
assertEquals(sum, 300);

In the end, we need to release the memory back to the OS by calling freeMemory().

最后,我们需要通过调用freeMemory()将内存释放回给操作系统。

7. CompareAndSwap Operation

7.CompareAndSwap操作

The very efficient constructs from the java.concurrent package, like AtomicInteger, are using the compareAndSwap() methods out of Unsafe underneath, to provide the best possible performance. This construct is widely used in the lock-free algorithms that can leverage the CAS processor instruction to provide great speedup compared to the standard pessimistic synchronization mechanism in Java.

来自java.concurrent包的非常高效的构造,如AtomicInteger,是使用compareAndSwap()方法出Unsafe下,以提供最佳性能。这种构造被广泛用于无锁算法中,与Java中的标准悲观同步机制相比,它可以利用CAS处理器指令来提供极大的速度提升。

We can construct the CAS based counter using the compareAndSwapLong() method from Unsafe:

我们可以使用compareAndSwapLong()方法构建基于CAS的计数器,该方法来自Unsafe

class CASCounter {
    private Unsafe unsafe;
    private volatile long counter = 0;
    private long offset;

    private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        return (Unsafe) f.get(null);
    }

    public CASCounter() throws Exception {
        unsafe = getUnsafe();
        offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter"));
    }

    public void increment() {
        long before = counter;
        while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
            before = counter;
        }
    }

    public long getCounter() {
        return counter;
    }
}

In the CASCounter constructor we are getting the address of the counter field, to be able to use it later in the increment() method. That field needs to be declared as the volatile, to be visible to all threads that are writing and reading this value. We are using the objectFieldOffset() method to get the memory address of the offset field.

CASCounter构造函数中,我们得到了计数器字段的地址,以便以后能够在increment()方法中使用它。该字段需要被声明为易失性,以便对所有正在写入和读取该值的线程可见。我们使用objectFieldOffset()方法来获取offset字段的内存地址。

The most important part of this class is the increment() method. We’re using the compareAndSwapLong() in the while loop to increment previously fetched value, checking if that previous value changed since we fetched it.

这个类中最重要的部分是increment() 方法。我们在while循环中使用compareAndSwapLong()来增加之前获取的值,检查之前的值是否在我们获取后发生了变化。

If it did, then we are retrying that operation until we succeed. There is no blocking here, which is why this is called a lock-free algorithm.

如果是这样,那么我们就会重试该操作,直到成功。这里没有阻塞,这就是为什么这被称为无锁算法。

We can test our code by incrementing the shared counter from multiple threads:

我们可以通过增加多个线程的共享计数器来测试我们的代码。

int NUM_OF_THREADS = 1_000;
int NUM_OF_INCREMENTS = 10_000;
ExecutorService service = Executors.newFixedThreadPool(NUM_OF_THREADS);
CASCounter casCounter = new CASCounter();

IntStream.rangeClosed(0, NUM_OF_THREADS - 1)
  .forEach(i -> service.submit(() -> IntStream
    .rangeClosed(0, NUM_OF_INCREMENTS - 1)
    .forEach(j -> casCounter.increment())));

Next, to assert that state of the counter is proper, we can get the counter value from it:

接下来,为了断定计数器的状态是正确的,我们可以从它那里得到计数器的值。

assertEquals(NUM_OF_INCREMENTS * NUM_OF_THREADS, casCounter.getCounter());

8. Park/Unpark

8.公园/非公园

There are two fascinating methods in the Unsafe API that are used by the JVM to context switch threads. When the thread is waiting for some action, the JVM can make this thread blocked by using the park() method from the Unsafe class.

Unsafe API中,有两个迷人的方法,被JVM用来切换线程的上下文。当线程在等待某些动作时,JVM可以通过使用Unsafe类中的park()方法使这个线程被阻塞。

It is very similar to the Object.wait() method, but it is calling the native OS code, thus taking advantage of some architecture specifics to get the best performance.

它与Object.wait()方法非常相似,但它是在调用本地操作系统的代码,因此利用了一些架构的特殊性来获得最佳性能。

When the thread is blocked and needs to be made runnable again, the JVM uses the unpark() method. We’ll often see those method invocations in thread dumps, especially in the applications which use thread pools.

当线程被阻塞并需要再次使其可运行时,JVM会使用unpark()方法。我们经常会在线程转储中看到这些方法的调用,尤其是在使用线程池的应用中。

9. Conclusion

9.结论

In this article, we were looking at the Unsafe class and its most useful constructs.

在这篇文章中,我们正在研究Unsafe类和它最有用的构造。

We saw how to access private fields, how to allocate off-heap memory, and how to use the compare-and-swap construct to implement lock-free algorithms.

我们看到了如何访问私有字段,如何分配堆外内存,以及如何使用比较和交换结构来实现无锁算法。

The implementation of all these examples and code snippets can be found over on GitHub – this is a Maven project, so it should be easy to import and run as it is.

所有这些例子和代码片段的实现都可以在GitHub上找到over–这是一个Maven项目,所以应该很容易导入并按原样运行。