美文网首页
Java中的Unsafe

Java中的Unsafe

作者: 多喝水JS | 来源:发表于2018-12-31 00:12 被阅读21次

简介

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/

相关文章

  • java中的Unsafe

    简书 占小狼转载请注明原创出处,谢谢! 前言 Java最初被设计为一种安全的受控环境。尽管如此,HotSpot还是...

  • Java中的Unsafe

    Java和C++语言的一个重要区别就是Java中我们无法直接操作一块内存区域,不能像C++中那样可以自己申请内存和...

  • Java中的Unsafe

    简介 Java是一种安全的编程语言,可以防止程序员犯许多愚蠢的错误,其中大多数错误都是基于内存管理的。但是,有一种...

  • Java中的Unsafe

    https://www.jianshu.com/p/db8dce09232d[https://www.jiansh...

  • Java中的Unsafe

    Java和C++语言的一个重要区别就是Java中我们无法直接操作一块内存区域,不能像C++中那样可以自己申请内存和...

  • 【安卓逆向】Java中的魔术类

    简单谈一谈Java中的Unsafe类 Unsafe类是啥? Java最初被设计为一种安全的受控环境。尽管如此,Ja...

  • java 中Unsafe介绍

    Unsafe 是位于sun.misc包下的一个类,它可以让我们直接访问系统内存资源、自主管理内存资源等,这些方法在...

  • Unsafe 与 LockSupport

    1.Unsafe java concurrent 包的基础是CAS, 而进行CAS操作的就是这个 Unsafe类....

  • Java中如何分配以及释放直接内存

    Java中如何使用直接内存? 使用未公开的Unsafe 使用NIO的ByteBuffer或者DirectByteB...

  • Java Part 3: SecurityManager

    翻译自:securitymanager Intro 在 Java 中可以用 sun.misc.Unsafe 类为所...

网友评论

      本文标题:Java中的Unsafe

      本文链接:https://www.haomeiwen.com/subject/jeyplqtx.html