Java concurrency and lock design and implementation details (6) - talk about Unsafe

What is the class UnSafe? I believe that people who are new to Java or who have not studied Java in depth should not know that this class exists. What is this class for? Why am I mentioning it in this series on concurrency?

The answer is very simple, that is, this class is very important for concurrency, of course, its importance is not only reflected in the support for concurrency. The reason why this article talks about UnSafe is because the UnSafe class is an important part of the synchronization queue AbstractQueuedSynchronizer, the core of synchronization in AbstractQueuedSynchronizer, and AbstractQueuedSynchronizer is the core of Java synchronization.

The UnSafe class is under the sun.misc package and is not part of the Java standard. However, many Java basic class libraries, including some widely used high-performance development libraries, use this class at the bottom layer, which plays a great role in improving the running efficiency of Java. Here are some libraries and frameworks that use UnSafe classes:

(1)Netty
(2)Hazelcast
(3)Cassandra
(4)Mockito / EasyMock / JMock / PowerMock
(5)Scala Specs
(6)Spock
(7)Robolectric
(8)Grails
(9)Neo4j
(10)Spring Framework
(11)Akka
(12)Apache Kafka
(13)Apache Wink
(14)Apache Storm
(15)Apache Hadoop

(16)Apache Continuum

........This list is very long, from which we also see that many widely used applications such as Hadoop, Kafka, Netty, etc. use UnSafe.

What exactly is UnSafe? Why are you using it?

In the final analysis, it is because UnSafe provides hardware-level atomic operations, which improves Java's ability to perform low-level operations. We know that Java is a type-safe language, and the Unsafe class enables the Java language to have the same ability to manipulate memory space as the pointer of the C language, but of course it also brings the problem of pointers.

Excessive use of the Unsafe class will increase the chance of errors, so it is not recommended by Java officials, and there are almost no official documents. Oracle is planning to remove the Unsafe class from Java 9, and if so it would be too big of a deal.


Just because UnSafe can break Java security, Oracle is determined to remove it for no reason. Here's a comment from their mailing list:
    "IMHO - sun.misc.Unsafe must die. It is "unsafe". It must be deprecated. Please ignore all theoretical (imaginary) fetters from now on Get on the right track."
From this, the engineer seems to have a baseless hatred of Unsafe. . .

Let's ignore the risks of UnSafe for now, after all, we're not in a hurry to use it ourselves. Let's take a look at what operations the UnSafe class can implement. The following are some of the core methods in the UnSafe class.

1 //Expand memory  
 2     public native long reallocateMemory(long address, long bytes);  
 3       
 4 // allocate memory  
 5     public native long allocateMemory(long bytes);  
 6       
 7 // release memory  
 8     public native void freeMemory(long address);  
 9       
10 //Set the value in the given memory block  
11     public native void setMemory(Object o, long offset, long bytes, byte value);  
12       
13 //Copy from one memory block to another memory block  
14     public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);  
15       
16 //Get the value, regardless of the access restrictions of java, others have similar getInt, getDouble, getLong, getChar, etc.  
17     public native Object getObject(Object o, long offset);  
18       
19 //Set the value, regardless of the access restrictions of java, others have similar putInt, putDouble, putLong, putChar, etc.  
20     public native void putObject(Object o, long offset);  
21       
22 //Get a local pointer from a given memory address, if it is not the allocateMemory method, the result will be undefined  
23     public native long getAddress(long address);  
24       
25 //Store a local pointer to a given memory address, if the address is not the allocateMemory method, the result will be undefined  
26     public native void putAddress(long address, long x);  
27       
28 //This method returns the memory address offset of the given field, this value is unique and fixed for the given filed  
29     public native long staticFieldOffset(Field f);  
30       
31 //Report the location of a given field, regardless of whether the field is private, public or protected type, used in conjunction with staticFieldBase  
32     public native long objectFieldOffset(Field f);  
33       
34 //Get the position of a given field  
35     public native Object staticFieldBase(Field f);  
36       
37 //Ensure that the given class is initialized, which often needs to be combined with the static field of the base class  
38     public native void ensureClassInitialized(Class c);  
39       
40 //The offset address of the first element of the array can be obtained  
41     public native int arrayBaseOffset(Class arrayClass);  
42       
43 //The conversion factor of the array can be obtained, that is, the incremental address of the elements in the array. Use arrayBaseOffset with arrayIndexScale to locate each element in an array in memory  
44     public native int arrayIndexScale(Class arrayClass);  
45       
46 //Get the number of pages in the local memory, this value is always the power of 2  
47     public native int pageSize();  
48       
49 //Tell the virtual machine to define a class without security checks. By default, this class loader and protection domain come from the caller class  
50     public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);  
51       
52 //Define a class, but don't let it know about the classloader and system dictionary  
53     public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);  
54       
55 //Lock the object, it must not be locked
56     public native void monitorEnter(Object o);  
57       
58 //Unlock the object  
59     public native void monitorExit(Object o);  
60       
61 //Attempt to lock the object, return true or false whether the lock is successful, if it is locked, it must be unlocked with monitorExit  
62     public native boolean tryMonitorEnter(Object o);  
63       
64 // Exception raised, no notification  
65     public native void throwException(Throwable ee);  
66       
67 //CAS, if the value on the object offset = the expected value, update to x, return true. Otherwise, false. Similar to compareAndSwapInt, compareAndSwapLong, compareAndSwapBoolean, compareAndSwapChar and so on.  
68     public final native boolean compareAndSwapObject(Object o, long offset,  Object expected, Object x);  
69       
70 // This method obtains the value of the integer field corresponding to the offset offset address in the object, and supports volatile load semantics. Similar methods are getIntVolatile, getBooleanVolatile, etc.  
71     public native Object getObjectVolatile(Object o, long offset);   
72       
73 //The thread calls this method, and the thread will block until it times out or an interrupt condition occurs.  
74     public native void park(boolean isAbsolute, long time);  
75       
76 //Terminate the suspended thread and return to normal. The suspended operations in the java.util.concurrent package are implemented in the LockSupport class, and these two methods are used
77     public native void unpark(Object thread);  
78       
79 //Get the load of the system at different times  
80     public native int getLoadAverage(double[] loadavg, int nelems);  
81       
82 //Create an instance of a class without calling its constructor, initialization code, various JVM security checks, and other low-level things. Even if the constructor is private, we can create an instance of it through this method. For the singleton pattern, it's a nightmare, haha  
83     public native Object allocateInstance(Class cls) throws InstantiationException;  

(1) Memory operation

This part includes allocateMemory (allocate memory), reallocateMemory (reallocate memory), copyMemory (copy memory), freeMemory (release memory), getAddress (get memory address), addressSize, pageSize, getInt (get the integer pointed to by the memory address), getIntVolatile (gets the integer pointed to by the memory address and supports volatile semantics), putInt (writes the integer to the specified memory address), putIntVolatile (writes the integer to the specified memory address and supports volatile semantics), putOrderedInt (writes the integer to the specified memory) address, ordered or delayed methods) etc. getXXX and putXXX contain various basic types of operations.

Using the copyMemory method, we can implement a general object copy method without implementing the clone method for each object. Of course, this general method can only achieve shallow object copying.

(2) Unconventional object instantiation

The allocateInstance() method provides another way to create an instance. Usually we can use new or reflection to instantiate objects, and use the allocateInstance() method to generate object instances directly without calling constructors and other initialization methods.

This can be useful when objects are deserialized, to be able to rebuild and set final fields without calling the constructor.

(3) Manipulating classes, objects, and variables

This part includes staticFieldOffset (static domain offset), defineClass (defined class), defineAnonymousClass (defined anonymous class), ensureClassInitialized (ensure class initialization), objectFieldOffset (object domain offset) and other methods. Through these methods, we can obtain the pointer of the object, and by offsetting the pointer, we can not only directly modify the data pointed to by the pointer (even if they are private), but even find the object that the JVM has identified as garbage and can be recycled.

(4) Array operation

This part includes methods such as arrayBaseOffset (getting the offset address of the first element of the array), arrayIndexScale (getting the incremental address of the elements in the array). ArrayBaseOffset is used in conjunction with arrayIndexScale to locate the location of each element in the array in memory.

(5) Multi-thread synchronization

This part includes monitorEnter, tryMonitorEnter, monitorExit, compareAndSwapInt, compareAndSwapLong, compareAndSwapObject and other CAS methods. However, monitorEnter, tryMonitorEnter and monitorExit have been marked as deprecated and are not recommended.

The CAS operation of the Unsafe class is probably the most used. It provides a new solution for Java's locking mechanism. For example, classes such as AtomicInteger are implemented by this method. The compareAndSwap method is atomic, which can avoid heavy locking mechanism and improve code efficiency. This is an optimistic lock, which is generally considered to avoid race conditions in most cases, and if the operation fails, it will continue to retry until it succeeds.

(6) Suspend and resume

This part includes methods such as park and unpark. Suspending a thread is implemented by the park method. After calling park, the thread will be blocked until a timeout or interruption occurs. unpark can terminate a suspended thread and bring it back to normal. The thread suspension operation in the entire concurrency framework is encapsulated in the LockSupport class. There are various versions of the pack method in the LockSupport class, but the Unsafe.park() method is eventually called.

(7) Memory barrier

After Java8, methods such as loadFence, storeFence, and fullFence were introduced to define memory barriers and avoid code reordering. loadFence() means that all load operations before this method are completed before the memory barrier. Similarly storeFence() means that all store operations before this method are completed before the memory barrier. fullFence() means that all load and store operations before this method are completed before the memory barrier.

The following are some C code implementations of the native methods in the UnSafe class, which are roughly as follows, you can simply look at them:

// natUnsafe.cc - Implementation of sun.misc.Unsafe native methods.

/** Copyright (C) 2006, 2007
   Free Software Foundation

   This file is part of libgcj.

This software is copyrighted work licensed under the terms of the
Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
details.  */

#include <gcj/cni.h>
#include <gcj/field.h>
#include <gcj/javaprims.h>
#include <jvm.h>
#include <sun/misc/Unsafe.h>
#include <java/lang/System.h>
#include <java/lang/InterruptedException.h>

#include <java/lang/Thread.h>
#include <java/lang/Long.h>

#include "sysdep/locks.h"

// Use a spinlock for multi-word accesses
class spinlock
{
  static volatile obj_addr_t lock;

public:

spinlock ()
  {
    while (! compare_and_swap (&lock, 0, 1))
      _Jv_ThreadYield ();
  }
  ~spinlock ()
  {
    release_set (&lock, 0);
  }
};
  
// This is a single lock that is used for all synchronized accesses if
// the compiler can't generate inline compare-and-swap operations.  In
// most cases it'll never be used, but the i386 needs it for 64-bit
// locked accesses and so does PPC32.  It's worth building libgcj with
// target=i486 (or above) to get the inlines.
volatile obj_addr_t spinlock::lock;


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;
}
  
static inline bool
compareAndSwap (volatile jlong *addr, jlong old, jlong new_val)
{
  jboolean result = false;
  spinlock lock;
  if ((result = (*addr == old)))
    *addr = new_val;
  return result;
}
  
static inline bool
compareAndSwap (volatile jobject *addr, jobject old, jobject new_val)
{
  jboolean result = false;
  spinlock lock;
  if ((result = (*addr == old)))
    *addr = new_val;
  return result;
}
  

jlong
sun::misc::Unsafe::objectFieldOffset (::java::lang::reflect::Field *field)
{
  _Jv_Field *fld = _Jv_FromReflectedField (field);
  // FIXME: what if it is not an instance field?
  return fld->getOffset();
}

jin
sun::misc::Unsafe::arrayBaseOffset (jclass arrayClass)
{
  // FIXME: assert that arrayClass is array.
  jclass eltClass = arrayClass->getComponentType();
  return (jint)(jlong) _Jv_GetArrayElementFromElementType (NULL, eltClass);
}

jin
sun::misc::Unsafe::arrayIndexScale (jclass arrayClass)
{
  // FIXME: assert that arrayClass is array.
  jclass eltClass = arrayClass->getComponentType();
  if (eltClass->isPrimitive())
    return eltClass->size();
  return sizeof (void *);
}

// These methods are used when the compiler fails to generate inline
// versions of the compare-and-swap primitives.

jboolean
sun::misc::Unsafe::compareAndSwapInt (jobject obj, jlong offset,
                      jint expect, jint update)
{
  jint *addr = (jint *)((char *)obj + offset);
  return compareAndSwap (addr, expect, update);
}

jboolean
sun::misc::Unsafe::compareAndSwapLong (jobject obj, jlong offset,
                       jlong expect, jlong update)
{
  volatile jlong *addr = (jlong*)((char *) obj + offset);
  return compareAndSwap (addr, expect, update);
}

jboolean
sun::misc::Unsafe::compareAndSwapObject (jobject obj, jlong offset,
                     jobject expect, jobject update)
{
  jobject *addr = (jobject*)((char *) obj + offset);
  return compareAndSwap (addr, expect, update);
}

void
sun::misc::Unsafe::putOrderedInt (jobject obj, jlong offset, jint value)
{
  volatile jint * addr = (jint *) ((char *) obj + offset);
  *addr = value;
}

void
sun::misc::Unsafe::putOrderedLong (jobject obj, jlong offset, jlong value)
{
  volatile jlong *addr = (jlong *) ((char *) obj + offset);
  spinlock lock;
  *addr = value;
}

void
sun::misc::Unsafe::putOrderedObject (jobject obj, jlong offset, jobject value)
{
  volatile jobject *addr = (jobject *) ((char *) obj + offset);
  *addr = value;
}

void
sun::misc::Unsafe::putIntVolatile (jobject obj, jlong offset, jint value)
{
  write_barrier ();
  volatile jint * addr = (jint *) ((char *) obj + offset);
  *addr = value;
}

void
sun::misc::Unsafe::putLongVolatile (jobject obj, jlong offset, jlong value)
{
  volatile jlong *addr = (jlong *) ((char *) obj + offset);
  spinlock lock;
  *addr = value;
}

void
sun::misc::Unsafe::putObjectVolatile (jobject obj, jlong offset, jobject value)
{
  write_barrier ();
  volatile jobject *addr = (jobject *) ((char *) obj + offset);
  *addr = value;
}

#if 0  // FIXME
void
sun::misc::Unsafe::putInt (jobject obj, jlong offset, jint value)
{
  jint *addr = (jint *) ((char *) obj + offset);
  *addr = value;
}
#endif

void
sun::misc::Unsafe::putLong (jobject obj, jlong offset, jlong value)
{
  jlong *addr = (jlong *) ((char *) obj + offset);
  spinlock lock;
  *addr = value;
}

void
sun::misc::Unsafe::putObject (jobject obj, jlong offset, jobject value)
{
  jobject *addr = (jobject *) ((char *) obj + offset);
  *addr = value;
}

jin
sun::misc::Unsafe::getIntVolatile (jobject obj, jlong offset)
{
  volatile jint * addr = (jint *) ((char *) obj + offset);
  jint result = *addr;
  read_barrier ();
  return result;
}

jobject
sun::misc::Unsafe::getObjectVolatile (jobject obj, jlong offset)
{
  volatile jobject *addr = (jobject *) ((char *) obj + offset);
  jobject result = *addr;
  read_barrier ();
  return result;
}

jlong
sun::misc::Unsafe::getLong (jobject obj, jlong offset)
{
  jlong *addr = (jlong *) ((char *) obj + offset);
  spinlock lock;
  return *addr;
}

jlong
sun::misc::Unsafe::getLongVolatile (jobject obj, jlong offset)
{
  volatile jlong *addr = (jlong *) ((char *) obj + offset);
  spinlock lock;
  return *addr;
}

void
sun::misc::Unsafe::unpark (::java::lang::Thread *thread)
{
  natThread *nt = (natThread *) thread->data;
  nt->park_helper.unpark ();
}

void
sun::misc::Unsafe::park (jboolean isAbsolute, jlong time)
{
  using namespace ::java::lang;
  Thread *thread = Thread::currentThread();
  natThread *nt = (natThread *) thread->data;
  nt->park_helper.park (isAbsolute, time);
}

Although we have said before that the UnSafe class is not safe to use, if you are sure that you have a deep understanding of what it wants to do with it, then of course it can be used. In the UnSafe class, it is designed as a singleton, And the general code cannot directly access it, only the caller like JDK will be considered as a legal caller, because the access control is carried out in the code, as shown below.

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

If we wish to use this class, the easiest way is probably through reflection in Java, like this:

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
So far, the general situation about the UnSafe class is almost described here. Thank you for reading. If you don't follow me, you can add a follow ^_^

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325915406&siteId=291194637