关于Unsafe类一些知识

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/java_yes/article/details/84344203

参考文章:
https://blog.csdn.net/fenglibing/article/details/17138079
https://www.cnblogs.com/pkufork/p/java_unsafe.html
我们看过了AtomicInteger类,会发现里面有个类:

private static final Unsafe unsafe = Unsafe.getUnsafe();

同时调用了一个方法:

valueOffset = unsafe.objectFieldOffset  (AtomicInteger.class.getDeclaredField("value"));

这个Unsafe类是干嘛的。参考文章:http://hllvm.group.iteye.com/group/topic/37940

Unsafe类意义:

sun.misc.Unsafe是JDK内部用的工具类。它通过暴露一些Java意义上说“不安全”的功能给Java层代码,来让JDK能够更多的使用Java代码来实现一些原本是平台相关的、需要使用native语言(例如C或C++)才可以实现的功能。该类不应该在JDK核心类库之外使用。

JVM的实现可以自由选择如何实现Java对象的“布局”,也就是在内存里Java对象的各个部分放在哪里,包括对象的实例字段和一些元数据之类。sun.misc.Unsafe里关于对象字段访问的方法把对象布局抽象出来,它提供了objectFieldOffset()方法用于获取某个字段相对Java对象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之类的方法可以使用前面获取的偏移量来访问某个Java对象的某个字段。

每个JVM都有自己的方式来实现Java对象的布局。Oracle/Sun HotSpot VM所使用的Java对象布局可以参考这篇博客:http://www.codeinstructions.com/2008/12/java-objects-memory-structure.html
(这篇内容其实并不是太完整但只是入门凑合看看是够了。另外它只针对32位的JDK6的HotSpot VM的默认配置。)

获取Unsafe类对象

首先看下Unsafe源码:


public final class Unsafe {
 
    private static native void registerNatives();
    static {
        registerNatives();
        sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
    }
 
    private Unsafe() {}
 
    private static final Unsafe theUnsafe = new Unsafe();
 
	@CallerSensitive
    public static Unsafe getUnsafe() {
        Class<?> caller = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(caller.getClassLoader()))
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }
    		.
    		.
    		.

我们可以看到Unsafe里面采用了单例模式,所以我们new对象的方式肯定是不行了。但虽然它提供了getUnsafe()方法,可是我们调用的时候会显示ExceptionInInitializerError异常信息,如下图所示:
异常
我们看getUnsafe()方法上面有一个注解:@CallerSensitive
所以我们通过反射或得Unsafe对象:

    private static  Unsafe UNSAFE = Unsafe.getUnsafe();

    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            UNSAFE = (Unsafe) field.get(null);
        } catch (Exception e) {
        }
    }

获取到Unsafe实例之后,我们就可以为所欲为了。

Unsafe类提供了以下这些功能:

一、内存管理。包括分配内存、释放内存等。

该部分包括了allocateMemory(分配内存)、reallocateMemory(重新分配内存)、copyMemory(拷贝内存)、freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)等方法。getXXX和putXXX包含了各种基本类型的操作。

利用copyMemory方法,我们可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现clone方法,当然这通用的方法只能做到对象浅拷贝。

二、非常规的对象实例化。

allocateInstance()方法提供了另一种创建实例的途径。通常我们可以用new或者反射来实例化对象,使用allocateInstance()方法可以直接生成对象实例,且无需调用构造方法和其它初始化方法。

这在对象反序列化的时候会很有用,能够重建和设置final字段,而不需要调用构造方法。

class User {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void test() {
        System.err.println("hello,world " + name);
    }
}


public class UnsafeTest {
    private static Unsafe UNSAFE;

    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            UNSAFE = (Unsafe) field.get(null);
        } catch (Exception e) {
        }
    }

    public static void main(String[] args) {
        try {
            User user = (User) UNSAFE.allocateInstance(User.class);
            user.setName("user1");
            System.out.println("instance: " + user);
            user.test();

            Field name = user.getClass().getDeclaredField("name");
            UNSAFE.putObject(user, UNSAFE.objectFieldOffset(name), "user2");
            user.test();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

三、操作类、对象、变量。

这部分包括了staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)等方法。

通过这些方法我们可以获取对象的指针,通过对指针进行偏移,我们不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。

四、数组操作。

这部分包括了arrayBaseOffset(获取数组第一个元素的偏移地址)、arrayIndexScale(获取数组中元素的增量地址)等方法。arrayBaseOffset与arrayIndexScale配合起来使用,就可以定位数组中每个元素在内存中的位置。

由于Java的数组最大值为Integer.MAX_VALUE,使用Unsafe类的内存分配方法可以实现超大数组。实际上这样的数据就可以认为是C数组,因此需要注意在合适的时间释放内存。

class SuperArray {
    private final static int BYTE = 1;
    private long size;
    private long address;
     
    public SuperArray(long size) {
        this.size = size;
        //得到分配内存的起始地址
        address = getUnsafe().allocateMemory(size * BYTE);
    }
    public void set(long i, byte value) {
        getUnsafe().putByte(address + i * BYTE, value);
    }
    public int get(long idx) {
        return getUnsafe().getByte(address + idx * BYTE);
    }
    public long size() {
        return size;
    }

五、多线程同步。包括锁机制、CAS操作等。

这部分包括了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap等方法。

其中monitorEnter、tryMonitorEnter、monitorExit已经被标记为deprecated,不建议使用。

Unsafe类的CAS操作可能是用的最多的,它为Java的锁机制提供了一种新的解决办法,比如AtomicInteger等类都是通过该方法来实现的。compareAndSwap方法是原子的,可以避免繁重的锁机制,提高代码效率。这是一种乐观锁,通常认为在大部分情况下不出现竞态条件,如果操作失败,会不断重试直到成功。

六、挂起与恢复。

这部分包括了park、unpark等方法。

将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。

七、内存屏障。

这部分包括了loadFence、storeFence、fullFence等方法。这是在Java 8新引入的,用于定义内存屏障,避免代码重排序。

loadFence() 表示该方法之前的所有load操作在内存屏障之前完成。同理storeFence()表示该方法之前的所有store操作在内存屏障之前完成。fullFence()表示该方法之前的所有load、store操作在内存屏障之前完成。

猜你喜欢

转载自blog.csdn.net/java_yes/article/details/84344203