Java中的Unsafe

简介

Java是一种安全的编程语言,可以防止程序员犯许多愚蠢的错误,其中大多数错误都是基于内存管理的。但是,有一种方法可以绕过这些限制,即使用 Unsafe class。可以手动操作内存,这样可以大大减少垃圾回收时间而且可以减少堆内内存的使用。

获取Unsafe对象

Unsafe类里面可以看到有一个getUnsafe方法:

    @CallerSensitive
public static Unsafe getUnsafe() {
  Class<?> caller = Reflection.getCallerClass();
  if (!VM.isSystemDomainLoader(caller.getClassLoader()))
      throw new SecurityException("Unsafe");
  return theUnsafe;
}

但是直接调用会抛出SecurityException不安全异常,因为这个getUnsafe方法会检查我们的代码是否由BootClassLoader加载了。很明显项目中所写的代码都是由Appclass Loader加载的。所以报错了。
如何做呢?
(1)我们可以使我们的代码“可信”。在运行程序时使用选项bootclasspath,把要使用Unsafe的类添加到系统类路径中。
例如:

java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.duoheshui.com.UnsafeTestClient

但是这样太麻烦了
(2)使用反射:

    public static Unsafe getUnsafe() {
        try {
            Field singletonInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
            singletonInstanceField.setAccessible(true);
            return (Unsafe) singletonInstanceField.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
Unsafe类的成员

除了上面谈及的getUnsafe会返回Unsafe实例theUnsafe外,Unsafe一共由105个方法组成,大部分都是native方法。下面是一些可能用到的方法:

返回低级别内存信息

addressSize()
pageSize()

手动获得对象和对象方法

allocateInstance() 避开构造方法生成对象
objectFieldOffset() 获得对象的某个成员的地址偏移量

手动获得类或者静态成员

staticFieldOffset() 获得某个静态成员的地址偏移量
defineClass()
defineAnonymousClass()
ensureClassInitialized()

手动获得数组

arrayBaseOffset()
arrayIndexScale()

同步的低级别基本方法

monitorEnter()
tryMonitorEnter()
monitorExit()
compareAndSwapInt()
putOrderedInt()

手动操作内存

allocateMemory()
copyMemory()
freeMemory()
getAddress()
getInt() ,getInt(Object var1, long var2)第一个参数是要get的对象,第二个参数是字段的偏移量
putInt()

阻塞和唤醒

pack()
unpack()

用法

1、CAS(compareAndSwap)

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
           //获取变量value的地址偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    //声明为volatile,有变化回写到主内存,其他线程再重新从主内存读取最新的数据,保持可见性
    private volatile int value;
    //比较并交换
     public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

sun.misc.Unsafe.compareAndSwapInt(Object, long, int, int)


2、避开构造方法初始化对象,使用allocateInstance

        Unsafe unsafe = getUnsafe();
        final Class userClass = User.class;
        User user = (User) unsafe.allocateInstance(userClass );

3、修改对象成员值,使用putInt()

        User a = new User(10);
        Field f = User.class.getDeclaredField("age");
        unsafe.putInt(a, unsafe.objectFieldOffset(f), 8);

4、获取对象地址
获取部门对象在内存中的地址偏移量

private DepartMent dept;
valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("dept"));
CAS(compareAndSwapInt)源码实现

JDK8 src/share/vm/prims/unsafe.cpp

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

将调用Atomic::cmpxchg(x, addr, e)进行对比交换,该方法在hotspot\src\share\vm\runtime\atomic.cpp中

inline jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest,
jbyte compare_value, cmpxchg_memory_order order) {
    STATIC_ASSERT(sizeof(jbyte) == 1);
    volatile jint* dest_int =
    static_cast<volatile jint*>(align_ptr_down(dest, sizeof(jint)));
    size_t offset = pointer_delta(dest, dest_int, 1);
    jint cur = *dest_int;
    jbyte* cur_as_bytes = reinterpret_cast<jbyte*>(&cur);
    // current value may not be what we are looking for, so force it
    // to that value so the initial cmpxchg will fail if it is different
    cur_as_bytes[offset] = compare_value;
    // always execute a real cmpxchg so that we get the required memory
    // barriers even on initial failure
    do {
        // value to swap in matches current value ...
        jint new_value = cur;
        // ... except for the one jbyte we want to update
        reinterpret_cast<jbyte*>(&new_value)[offset] = exchange_value;
        jint res = cmpxchg(new_value, dest_int, cur, order);
        if (res == cur) break; // success
        // at least one jbyte in the jint changed value, so update
        // our view of the current jint
        cur = res;
        // if our jbyte is still as cur we loop and try again
    } while (cur_as_bytes[offset] == compare_value);
    return cur_as_bytes[offset];
}

大意就是先去获取一次结果,如果结果和现在不同,就直接返回,因为有其他人修改了;否则会一直尝试去修改。直到成功。

参考

http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容