sun.misc.Unsafe的理解

来源:互联网 发布:保健品网络推广怎么做 编辑:程序博客网 时间:2024/05/11 15:42

前言

以下sun.misc.Unsafe源码和demo基于jdk1.7;

最近在看J.U.C里的源码,很多都用到了sun.misc.Unsafe这个类,一知半解,看起来总感觉有点不尽兴,所以打算对Unsafe的源码及使用做个分析;

另外,网上找了份c++的源代码natUnsafe.cc(可惜比较老,Copyright (C) 2006, 2007年的,没找到新的),也就是sun.misc.Unsafe的C++实现,跟Unsafe类中的native方法对照起来看更加容易理解;

Unsafe类的作用

可以用来在任意内存地址位置处读写数据,可见,对于普通用户来说,使用起来还是比较危险的;

另外,还支持一些CAS原子操作;

获取Unsafe对象

遗憾的是,Unsafe对象不能直接通过new Unsafe()或调用Unsafe.getUnsafe()获取,原因如下:

*不能直接new Unsafe(),原因是Unsafe被设计成单例模式,构造方法是私有的;

*不能通过调用Unsafe.getUnsafe()获取,因为getUnsafe被设计成只能从引导类加载器(bootstrap class loader)加载,从getUnsafe的源码中也可以看出来,如下:

复制代码
    @CallerSensitive    public static Unsafe getUnsafe() {        //得到调用该方法的Class对象        Class cc = Reflection.getCallerClass();        //判断调用该方法的类是否是引导类加载器(bootstrap class loader)        //如果不是的话,比如由AppClassLoader调用该方法,则抛出SecurityException异常        if (cc.getClassLoader() != null)            throw new SecurityException("Unsafe");        //返回单例对象        return theUnsafe;    }
复制代码

虽然我们不能通过以上方法得到Unsafe对象,但得Unsafe类中有个私有的静态全局属性theUnsafe(Unsafe实例对象,通过反射,可以获取到该成员属性theUnsafe对应的Field对象,并将其设置为可访问,从而得到theUnsafe具体对象,如下代码:

复制代码
package concurrency;import java.lang.reflect.Field;import sun.misc.Unsafe;import sun.reflect.Reflection;public class Test {    public static void main(String[] args) throws NoSuchFieldException,            SecurityException, IllegalArgumentException, IllegalAccessException {        // 通过反射得到theUnsafe对应的Field对象        Field field = Unsafe.class.getDeclaredField("theUnsafe");        // 设置该Field为可访问        field.setAccessible(true);        // 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的        Unsafe unsafe = (Unsafe) field.get(null);        System.out.println(unsafe);    }}
复制代码

 Unsafe类中的API

allocateInstance方法,不调用构造方法生成对象

 本地方法,功能是生成一个对象实例,但是不会运行该对象的构造方法;由于natUnsafe.cc版本较老,没找到对应的c++实现;

    /** Allocate an instance but do not run any constructor. Initializes the class if it has not yet been. */    public native Object allocateInstance(Class cls)        throws InstantiationException;

例子,利用Unsafe的allocateInstance方法,在未调用构造方法的情况下生成了对象:

复制代码
package concurrency;import java.lang.reflect.Field;import sun.misc.Unsafe;import sun.reflect.Reflection;class User {    private String name = "";    private int age = 0;    public User() {        this.name = "test";        this.age = 22;    }        @Override    public String toString() {        return name + ": " + age;    }}public class Test {    public static void main(String[] args) throws NoSuchFieldException,            SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {        // 通过反射得到theUnsafe对应的Field对象        Field field = Unsafe.class.getDeclaredField("theUnsafe");        // 设置该Field为可访问        field.setAccessible(true);        // 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的        Unsafe unsafe = (Unsafe) field.get(null);        User user = (User) unsafe.allocateInstance(User.class);        System.out.println(user); //dont invoke constructor, print null: 0                User userFromNormal = new User();        System.out.println(userFromNormal); //print test: 22    }}
复制代码

objectFieldOffset方法,返回成员属性在内存中的地址相对于对象内存地址的偏移量

比较简单,就是返回成员属性内存地址相对于对象内存地址的偏移量,通过该方法可以计算一个对象在内存中的空间大小,方法是通过反射得到它的所有Field(包括父类继承得到的),找出Field中偏移量最大值,然后对该最大偏移值填充字节数即为对象大小;

关于该方法的使用例子可以看下面的修改内存数据的例子;

putLong,putInt,putDouble,putChar,putObject等方法,直接修改内存数据(可以越过访问权限)

这里,还有put对应的get方法,很简单就是直接读取内存地址处的数据,不做举例;

 我们可以举个putLong(Object, long, long)方法详细看下其具体实现,其它的类似,先看Java的源码,没啥好看的,就声明了一个native本地方法:

三个参数说明下:

Object o//对象引用
long offset//对象内存地址的偏移量
long x//写入的数据
    public native void    putLong(Object o, long offset, long x);

还是看下natUnsafe.cc中的c++实现吧,很简单,就是计算要写入数据的内存地址,然后写入数据,如下:

复制代码
voidsun::misc::Unsafe::putLong (jobject obj, jlong offset, jlong value){  jlong *addr = (jlong *) ((char *) obj + offset);//计算要修改的数据的内存地址=对象地址+成员属性地址偏移量  spinlock lock;//自旋锁,通过循环来获取锁, i386处理器需要加锁访问64位数据,如果是int,则不需要改行代码  *addr = value;//往该内存地址位置直接写入数据}
复制代码

如下例子,即使User类的成员属性是私有的且没有提供对外的public方法,我们还是可以直接在它们的内存地址位置处写入数据,并成功;

复制代码
package concurrency;import java.lang.reflect.Field;import sun.misc.Unsafe;import sun.reflect.Reflection;class User {    private String name = "test";     private long id = 1;    private int age = 2;    private double height = 1.72;        @Override    public String toString() {        return name + "," + id + "," + age + "," + height;    }}public class Test {    public static void main(String[] args) throws NoSuchFieldException,            SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {        // 通过反射得到theUnsafe对应的Field对象        Field field = Unsafe.class.getDeclaredField("theUnsafe");        // 设置该Field为可访问        field.setAccessible(true);        // 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的        Unsafe unsafe = (Unsafe) field.get(null);        User user = new User();        System.out.println(user); //打印test,1,2,1.72                Class userClass = user.getClass();        Field name = userClass.getDeclaredField("name");        Field id = userClass.getDeclaredField("id");        Field age = userClass.getDeclaredField("age");        Field height = userClass.getDeclaredField("height");        //直接往内存地址写数据        unsafe.putObject(user, unsafe.objectFieldOffset(name), "midified-name");        unsafe.putLong(user, unsafe.objectFieldOffset(id),100l);        unsafe.putInt(user, unsafe.objectFieldOffset(age), 101);        unsafe.putDouble(user, unsafe.objectFieldOffset(height), 100.1);                System.out.println(user);//打印midified-name,100,101,100.1    }}
复制代码

 copyMemory、freeMemory

copyMemory:内存数据拷贝

freeMemory:用于释放allocateMemory和reallocateMemory申请的内存

 CAS操作的方法,compareAndSwapInt,compareAndSwapLong等

看下natUnsafe.cc中的c++实现吧,加深理解,其实就是将内存值与预期值作比较,判断是否相等,相等的话,写入数据,不相等不做操作,返回旧数据;

复制代码
static inline boolcompareAndSwap (volatile jint *addr, jint old, jint new_val){  jboolean result = false;  spinlock lock;  if ((result = (*addr == old)))    *addr = new_val;  return result;}
复制代码

J.U.C里原子类就是基于以上CAS操作实现的;

getLongVolatile/putLongVolatile等等方法

这类方法使用volatile语义去存取数据,我的理解就是各个线程不缓存数据,直接在内存中读取数据;

 

 参考连接:

https://github.com/aeste/gcc/blob/master/libjava/sun/misc/natUnsafe.cc

http://ifeve.com/sun-misc-unsafe/

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/misc/Unsafe.java

 


@Author      风一样的码农
@HomePageUrl http://www.cnblogs.com/chenpi/ 
@Copyright      转载请注明出处,谢谢~ 
分类: JAVA 底层
标签: JAVA
好文要顶 关注我 收藏该文  
风一样的码农
关注 - 4
粉丝 - 250
+加关注
1
0
« 上一篇:jstack简单使用,定位死循环、线程阻塞、死锁等问题
» 下一篇:类加载机制
posted @ 2016-04-14 17:09 风一样的码农 阅读(3006) 评论(3) 编辑 收藏

  
#1楼 2016-12-01 17:04 BiuBiuBong  
谢谢楼主分享
支持(0)反对(0)
  
#2楼 2017-01-18 14:37 zhaoch93  
楼主你好。我像请教下unsafe对于数组使用获得的arrayBaseOffset和arrayIndexScale是具体指什么首地址和增量。这个是否是仅仅根据数组的class文件计算出来的?那是否是针对不同的虚拟机实现不同,但是针对确定的虚拟机始终是固定的值?那如何绑定实例使用这两个值。我不太理解这两个值在具体的实例中表示什么。我觉得我在这方面知识缺少的比较多,如果您比较忙的话,可否指教下这部分内容有没有什么好的方法系统的学习下?
支持(0)反对(0)
  
#3楼[楼主2017-01-18 15:34 风一样的码农  
@ zhaoch93
arrayBaseOffset应该是数组在内存中的起始地址,arrayIndexScale是数组中存放的每个元素的内存地址相对于数组起始地址的偏移量,这些地址不是固定的,跟字节码应该没关系,内存分配应该是运行的时候从堆中动态申请的;
个人理解,仅供参考~
网上资料好像不是很多,你可以找份natUnsafe.cc,看下源码,里面有具体实现;
原创粉丝点击