Java中Unsafe类详解

Java Unsafe 类详解
本文详细介绍了Java中的Unsafe类,探讨了其源码获取途径、主要功能及使用方式。Unsafe类虽然不对外开放源码,但在Java的基础类库和高性能开发库中扮演了重要角色。

Unsafe是不开放源码的,但是我们可以在openjdk中找到Unsafe的源码,下载openjdk1.7或者1.8的源码,在jdk\src\share\classes\sun\misc目录下可以找到Unsafe.java文件,而openjdk的源码我这里提供三种可选方案供同学们选择:

  1. 通过Mercurial代码版本管理工具从Repository中直接取得源码(Repository地址:http://hg.openjdk.java.net/jdk7/jdk7)(这个其实很方便,但是很多同学不用Mercurial,建议使用)
  2. 直接下载官方打包好的源码包了,可以从Source Releases页面(地址:http://download.java.Net/openjdk/jdk7/)取得打包好的源码(本人亲测,似乎不行)
  3. 给你个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;  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值