Unsafe是不开放源码的,但是我们可以在openjdk中找到Unsafe的源码,下载openjdk1.7或者1.8的源码,在jdk\src\share\classes\sun\misc目录下可以找到Unsafe.java文件,而openjdk的源码我这里提供三种可选方案供同学们选择:
- 通过Mercurial代码版本管理工具从Repository中直接取得源码(Repository地址:http://hg.openjdk.java.net/jdk7/jdk7)(这个其实很方便,但是很多同学不用Mercurial,建议使用)
- 直接下载官方打包好的源码包了,可以从Source Releases页面(地址:http://download.java.Net/openjdk/jdk7/)取得打包好的源码(本人亲测,似乎不行)
- 给你个csdn的下载地址用一个积分去换取吧http://download.youkuaiyun.com/download/lirenzuo/9836324
Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。Oracle正在计划从Java 9中去掉Unsafe类,如果真是如此影响就太大了。
java不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作,主要提供了以下功能(共计82个public native方法,这里对其进行一个简单的分类,我们会在末尾给出基本的方法原代码):
1、对内存的操作;
类中提供的3个本地方法allocateMemory、reallocateMemory、freeMemory分别用于分配内存,扩充内存和释放内存,与C语言中的3个方法对应。
public native long allocateMemory(long l);
public native long reallocateMemory(long l, long l1);
public native void freeMemory(long l);
2、可以定位对象某字段的内存位置,也可以修改对象的字段值,即使它是私有的;
字段的定位:
JAVA中对象的字段的定位可能通过staticFieldOffset方法实现,该方法返回给定field的内存地址偏移量,这个值对于给定的filed是唯一的且是固定不变的。
getIntVolatile方法获取对象中offset偏移地址对应的整型field的值,支持volatile load语义。
getLong方法获取对象中offset偏移地址对应的long型field的值
数组元素定位:
Unsafe类中有很多以BASE_OFFSET结尾的常量,比如ARRAY_INT_BASE_OFFSET,ARRAY_BYTE_BASE_OFFSET等,这些常量值是通过arrayBaseOffset方法得到的。arrayBaseOffset方法是一个本地方法,可以获取数组第一个元素的偏移地址。Unsafe类中还有很多以INDEX_SCALE结尾的常量,比如 ARRAY_INT_INDEX_SCALE , ARRAY_BYTE_INDEX_SCALE等,这些常量值是通过arrayIndexScale方法得到的。arrayIndexScale方法也是一个本地方法,可以获取数组的转换因子,也就是数组中元素的增量地址。将arrayBaseOffset与arrayIndexScale配合使用,可以定位数组中每个元素在内存中的位置。
public final class Unsafe {
public static final int ARRAY_INT_BASE_OFFSET;
public static final int ARRAY_INT_INDEX_SCALE;
public native long staticFieldOffset(Field field);
public native int getIntVolatile(Object obj, long l);
public native long getLong(Object obj, long l);
public native int arrayBaseOffset(Class class1);
public native int arrayIndexScale(Class class1);
static{
ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset([I);
ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale([I);
}
}
3、挂起与恢复
将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。
4、CAS操作
是通过compareAndSwapXXX方法实现的
/**
* 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。
*
* @param obj 需要更新的对象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值
* @return 如果field的值被更改返回true
*/
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
首先介绍一下什么是Compare And Swap(CAS)?简单的说就是比较并交换。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。” Java并发包(java.util.concurrent)中大量使用了CAS操作,涉及到并发的地方都调用了sun.misc.Unsafe类方法进行CAS操作。
在看一下volatile, Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的值是相同的,更简单一点理解就是volatile修饰的变量值发生变化时对于另外的线程是可见的。
如何获得Unsafe实例对象
通常我们最好也不要使用Unsafe类,除非有明确的目的,并且也要对它有深入的了解才行。Unsafe类使用了单例模式,需要通过一个静态方法getUnsafe()来获取。但Unsafe类做了限制,如果是普通的调用的话,它会抛出一个SecurityException异常;只有由bootstrap class loader加载的类才能调用这个方法。其源码如下:
@CallerSensitive
public static Unsafe getUnsafe() {
Class cc = Reflection.getCallerClass();
//返回null代表该类是bootstrap ClassLoader加载的
if (cc.getClassLoader() != null)
throw new SecurityException("Unsafe");
return theUnsafe;
}
下面这个例子演示了简单的修改一个byte[]的数据。因为sun.misc.Unsafe包不能直接使用,所有代码里用反射的技巧得到了一个Unsafe的实例。注:在你的开发集成工具里要设置Java编译属性(博主的是eclipse):项目右键–>Properties–>选择java compiler–>选择errors/warnings–>点击Deprecated and restricted API下拉–>修改Forbidden reference(access rules)属性为warning
import java.lang.reflect.Field;
import java.util.Arrays;
import sun.misc.Unsafe;
public class Test {
private static int byteArrayBaseOffset;
public static void main(String[] args) throws SecurityException,
NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe UNSAFE = (Unsafe) theUnsafe.get(null);
System.out.println(UNSAFE);
byte[] data = new byte[10];
System.out.println(Arrays.toString(data));
byteArrayBaseOffset = UNSAFE.arrayBaseOffset(byte[].class);
System.out.println(byteArrayBaseOffset);
UNSAFE.putByte(data, byteArrayBaseOffset, (byte) 1);
UNSAFE.putByte(data, byteArrayBaseOffset + 5, (byte) 5);
System.out.println(Arrays.toString(data));
}
}
运行结果:
sun.misc.Unsafe@1ab2f2d6
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
16
[1, 0, 0, 0, 0, 5, 0, 0, 0, 0]
列出Unsafe的主要方法
1 //扩充内存
2 public native long reallocateMemory(long address, long bytes);
3
4 //分配内存
5 public native long allocateMemory(long bytes);
6
7 //释放内存
8 public native void freeMemory(long address);
9
10 //在给定的内存块中设置值
11 public native void setMemory(Object o, long offset, long bytes, byte value);
12
13 //从一个内存块拷贝到另一个内存块
14 public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
15
16 //获取值,不管java的访问限制,其他有类似的getInt,getDouble,getLong,getChar等等。获取o对象中offset偏移地址对应的int/double/long/char/object...型field的值
17 public native Object getObject(Object o, long offset);
18
19 //设置值,不管java的访问限制,其他有类似的putInt,putDouble,putLong,putChar等等
20 public native void putObject(Object o, long offset);
21
22 //从一个给定的内存地址获取本地指针,如果不是allocateMemory方法的,结果将不确定
23 public native long getAddress(long address);
24
25 //存储一个本地指针到一个给定的内存地址,如果地址不是allocateMemory方法的,结果将不确定
26 public native void putAddress(long address, long x);
27
28 //该方法返回给定field的内存地址偏移量,这个值对于给定的filed是唯一的且是固定不变的
29 public native long staticFieldOffset(Field f);
30
31 //报告一个给定的字段的位置,不管这个字段是private,public还是保护类型,和staticFieldBase结合使用
32 public native long objectFieldOffset(Field f);
33
34 //获取一个给定字段的位置
35 public native Object staticFieldBase(Field f);
36
37 //确保给定class被初始化,这往往需要结合基类的静态域(field)
38 public native void ensureClassInitialized(Class c);
39
40 //可以获取数组第一个元素的偏移地址
41 public native int arrayBaseOffset(Class arrayClass);
42
43 //可以获取数组的转换因子,也就是数组中元素的增量地址。将arrayBaseOffset与arrayIndexScale配合使用, 可以定位数组中每个元素在内存中的位置
44 public native int arrayIndexScale(Class arrayClass);
45
46 //获取本机内存的页数,这个值永远都是2的幂次方
47 public native int pageSize();
48
49 //告诉虚拟机定义了一个没有安全检查的类,默认情况下这个类加载器和保护域来着调用者类
50 public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
51
52 //定义一个类,但是不让它知道类加载器和系统字典
53 public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);
54
55 //锁定对象,必须是没有被锁的
56 public native void monitorEnter(Object o);
57
58 //解锁对象
59 public native void monitorExit(Object o);
60
61 //试图锁定对象,返回true或false是否锁定成功,如果锁定,必须用monitorExit解锁
62 public native boolean tryMonitorEnter(Object o);
63
64 //引发异常,没有通知
65 public native void throwException(Throwable ee);
66
67 //CAS,如果对象偏移量上的值=期待值,更新为x,返回true.否则false.类似的有compareAndSwapInt,compareAndSwapLong,compareAndSwapBoolean,compareAndSwapChar等等。
68 public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
69
70 // 该方法获取对象中offset偏移地址对应的整型field的值,支持volatile load语义。类似的方法有getIntVolatile,getBooleanVolatile等等
71 public native Object getObjectVolatile(Object o, long offset);
72
73 //线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现。
74 public native void park(boolean isAbsolute, long time);
75
76 //终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的,也正是使用这两个方法
77 public native void unpark(Object thread);
78
79 //获取系统在不同时间系统的负载情况
80 public native int getLoadAverage(double[] loadavg, int nelems);
81
82 //创建一个类的实例,不需要调用它的构造函数、初使化代码、各种JVM安全检查以及其它的一些底层的东西。即使构造函数是私有,我们也可以通过这个方法创建它的实例,对于单例模式,简直是噩梦,哈哈
83 public native Object allocateInstance(Class cls) throws InstantiationException;