java中的unSafe类

UnSafe的简单介绍

sun.misc.Unsafe,提供了一些可以直接操控内存和线程的底层操作。Unsafe被JDK广泛应用于java.nio和并发包等实现中。

因为Unsafe很不安全,所以JDK开发者增加了很多特殊限制来访问它。比如在Unsafe的getUnsafe在方法上有一个@CallerSensitive注解,该注解表示该方法的调用,需要调用者被该方法信任。

  1. 私有的构造器
  2. 静态方法getUnsafe()的调用器只能被Bootloader加载,否则抛出SecurityException 异常。不过,我们可以通过反射机制轻松获取Unsafe的一个实例。
public static Unsafe getUnsafe() {
   try {
           Field f = Unsafe.class.getDeclaredField("theUnsafe");
           f.setAccessible(true);
           return (Unsafe)f.get(null);
   } catch (Exception e) {
       /* ... */
   }
}

Unsafe类中的有用方法

allocateInstance方法

实例化一个类,当你要跳过对象初始化阶段或者避开构造器中的安全检查或者你想实例化一个没有公开构造器的类可以使用此方法。而由于allocateInstance,避开了类的初始化,则单例模式就受到的影响。

public class UnsafeTest {
    public static void main(String[] args) throws Exception {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);//theUnsafe是一个静态成员所以get方法中传入null
        A a = (A) unsafe.allocateInstance(A.class);//跳过了构造方法
        System.out.println(a.toString());
    }
}

class A{
    private A(){
        System.out.println("使用unsafe.allocateInstance不会打印此内容!");
    }
    public String toString(){
        return "调用了A.toString()方法";
    }
}

staticFieldOffset、objectFieldOffset以及putObject、putInt方法

public class UnsafeTest1 {
    public static void main(String[] args) throws Exception {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);

        //字段值的获取
        //1.1 静态字段获取 ;说明:静态字段的偏移量相对于该类的内存地址,即相对于 className.class 返回的对象;
        long staticFieldOffset = unsafe.staticFieldOffset(B.class.getDeclaredField("staticField"));
        //1.2 非静态字段 ;说明:该偏移量相对于该类的实例化对象的内存地址。
        long unstaticFieldOffset = unsafe.objectFieldOffset(B.class.getDeclaredField("objectField"));
        System.out.println("静态变量相对于类内存地址的偏移量 = " + staticFieldOffset);
        System.out.println("非静态变量相对于实例化对象的偏移量 = " + unstaticFieldOffset);

        //字段值的设置
        //1.3 修改非基本数据类型的值,使用:putObject(object , offset , value); 这里修改实例化对象b对应偏移地址字段的值;
        B b  = new B();
        unsafe.putObject(b, unstaticFieldOffset, "hello world!");
        //1.3 修改基本数据类型的值,使用对应类型的put方法,如:int 使用 putInt(object , offset , value);
        unsafe.putInt(B.class, staticFieldOffset, 20);

        System.out.println("静态变量被修改后的值 = " +B.getStaticField());
        System.out.println("非静态变量被修改后的值 = " + b.getObjectField());
    }
}

class B {
    private static int  staticField = 1;
    private String  objectField = "hello";

    public static int getStaticField() {
        return staticField;
    }
    public String getObjectField() {
        return objectField;
    }
}

allocateMemory、reallocateMemory、freeMemory

Unsafe 内存的使用:申请allocateMemory(long)、扩展reallocateMemory(long,long)、销毁freeMemory(long)、插入值putXXX()、获取值getXXX()。

注意:Unsafe申请的内存的使用将直接脱离jvm,gc将无法管理Unsafe申请的内存,所以使用之后一定要手动释放内存,避免内存溢出!!!

示例代码如下:

public class UnsafeTest2 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);

        //2、内存使用。说明:该内存的使用将直接脱离jvm,gc将无法管理以下方式申请的内存,以用于一定要手动释放内存,避免内存溢出;
        //2.1 向本地系统申请一块内存地址; 使用方法allocateMemory(long capacity) ,该方法将返回内存地址的起始地址
        long address = unsafe.allocateMemory(8);
        System.out.println("allocate memory address = " + address);

        //2.2  向内存地址中设置值;
        //2.2 说明: 基本数据类型的值的添加,使用对应put数据类型方法,如:添加byte类型的值,使用:putByte(内存地址 , 值);
        unsafe.putByte(address, (byte)1);

        //2.2 添加非基本数据类型的值,使用putObject(值类型的类类型 , 内存地址 , 值对象);
        unsafe.putObject(C.class, address+2, new C("1"));

        //2.3 从给定的内存地址中取出值, 同存入方法基本类似,基本数据类型使用getXX(地址) ,object类型使用getObject(类类型,地址);
        byte b = unsafe.getByte(address);
        System.out.println(b);

        //2.3 获取object类型值
        C c = (C) unsafe.getObject(C.class, address+2);
        System.out.println(c);

        //2.4 重新分配内存 reallocateMemory(内存地址 ,大小) , 该方法说明 :该方法将释放掉给定内存地址所使用的内存,并重新申请给定大小的内存;
        // 注意:会释放掉原有内存地址,但已经获取并保存的值任然可使用,原因:个人理解:使用unsafe.getXXX方法获取的是该内存地址的值,
        //并把值赋值给左边对象,这个过程相当于是一个copy过程--- 将系统内存的值 copy 到jvm 管理的内存中;
        long newAddress = unsafe.reallocateMemory(address, 32);
        System.out.println("new address = "+ newAddress);
        //再次调用,内存地址的值已丢失; 被保持与jvm中的对象值不被丢失;
        System.out.println("local memory value =" + unsafe.getByte(address) + " jvm memory value = "+ b);

        //2.5 使用申请过的内存;
        //说明: 该方法同reallocateMemory 释放内存的原理;
        unsafe.freeMemory(newAddress);

        //2.5 put 方法额外说明
        //putXXX() 方法中存在于这样的重载: putXXX(XXX ,long , XXX) ,如:putInt(Integer ,long , Integer) 或者 putObject(Object ,long ,Object)
        //个人理解 : 第一个参数相当于作用域,即:第三个参数所代表的值,将被存储在该域下的给定内存地址中;(此处疑惑:
        //如果unsafe是从操作系统中直接获取的内存地址,那么该地址应该唯一,重复在该地址存储数据,后者应该覆盖前者,但是并没有;应该是jvm有特殊处理,暂未研究深入,所以暂时理解为域;)
        //以下示例可以说明,使用allocateMemory申请的同一地址,并插入不同对象所表示的值,后面插入的值并没有覆盖前面插入的值;
        long taddress = unsafe.allocateMemory(1);
        C l = new C("l");
        C l1 = new C("l1");
        unsafe.putObject(l, taddress, l);
        System.out.println(unsafe.getObject(l, taddress));
        unsafe.putObject(l1, taddress, l1);
        System.out.println(unsafe.getObject(l1, taddress));
        System.out.println(unsafe.getObject(l, taddress));
        unsafe.putObject(C.class, taddress, new C("33"));
        System.out.println(unsafe.getObject(C.class, taddress));

        unsafe.freeMemory(taddress);
    }
}

class C {
    private String value;

    public C(String value) {
        this.value = value;
    }
}

CAS操作

CAS 操作(CAS,compare and swap的缩写,意:比较和交换):硬件级别的原子性更新变量;在Unsafe 中主要有三个方法:CompareAndSwapInt() ,CompareAndSwapLong() ,CompareAndSwapObject();

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        //3.0关于并发对变量的原子操作,请查看其它资料;
        /**
         * unsafe 提供硬件级别的原子操作CAS方法,如:compareAndSwapInt(Object ,long ,int ,int)
         * 说明:
         * 第一个参数:需要更新的对象;
         * 第二个参数:偏移地址;
         * 第三个对象:预期在该偏移地址上的当前值,即:getInt(obj,偏移地址) == 预期值;
         * 第四个参数:需要更新的值
         *
         * 当且仅当当前偏移量的值等于预期值时,才更新为给定值;否则不做任何改变;
         */

        //compareAndSwapObject 和 compareAndSwapLong 与下述示例类似;
        long offset = unsafe.allocateMemory(1);

        unsafe.putInt(Integer.class, offset, 1);

        System.out.println(unsafe.getInt(Integer.class, offset));
        boolean updateState = unsafe.compareAndSwapInt(Integer.class, offset, 1, 5);
        System.out.println("update state = "+ updateState +" ; value = " + unsafe.getInt(Integer.class,offset));

        unsafe.freeMemory(offset);
    }

park()和unpark()

线程挂起和恢复park()和unpark()

public class UnsafeTest4 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        /**
         * 4.1  unsafe提供线程挂起和恢复的原语;
         * part(boolean abs,long timeout)
         * 方法说明:
         *     将当前线程挂起,直到当期时间“到达”(即给定的timeout时间是一个时间点,该时间点从1970计数开始)
         *     timeout描述的时间点,或者等待线程中断或unpark;
         * 参数说明:
         *     abs 为false 时,表示timeout以纳秒为单位 ;当为false是,可设置timeout为0,表示永远挂起,直到interrupt 或则 unpark
         *     abs 为true 时,表示timeout以毫秒为单位;注意,经测试在abs为true时,将timeout设置为0,线程会立即返回;
         *     timeout : 指定线程挂起到某个时间点,该时间点从1970计数开始;
         * */
        Thread thread = new Thread(()->{
            unsafe.park(false, 0);//永远挂起
        });
        thread.start();

        /**
         * 4.2 恢复线程,方法如下:
         * unpark(Object thread);
         * 方法说明: 
         *      给与传入对象一个运行的许可,即将给定的线程从挂起状态恢复到运行状态;
         * 参数说明:
         *      thread :通常是一个线程对象;
         * 特殊说明:
         *      unpark 可以在park之前使用,但不论在park方法之前,进行了多少次的调用unpark方法,
         *      对于作为参数的thread线程始终将只获得一个运行许可;
         *      即:当park方法调用时,检测到该线程存在一个运行许可,park方法也会立即返回;
         */
        unsafe.unpark(thread);//恢复线程
    }

参考文章

转载于:https://my.oschina.net/cqqcqqok/blog/2050166

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UnsafeJava一个非常特殊的,它提供了对底层操作的支持,包括内存操作、线程调度等。使用Unsafe可以实现一些Java无法实现的操作,并且可以提高代码的性能。 CAS(Compare and Swap)操作是Unsafe非常常见的操作之一。它的作用是比较当前值和期望值是否相等,如果相等,则将新值更新到当前值,否则不做任何操作。CAS操作通常被用于多线程编程的数据同步,以保证多个线程对共享变量的操作是正确的。 下面是使用Unsafe进行CAS操作的一个示例: ``` import sun.misc.Unsafe; import java.lang.reflect.Field; public class CASDemo { private static Unsafe unsafe; static { try { // 使用反射获取Unsafe Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); unsafe = (Unsafe) field.get(null); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { // 假设初始值为10 int expectedValue = 10; // 将初始值存储在内存地址100的位置 long offset = unsafe.objectFieldOffset(CASDemo.class.getDeclaredFields()[0]); // 使用CAS操作将值从10更新为20 boolean casResult = unsafe.compareAndSwapInt(new CASDemo(), offset, expectedValue, 20); System.out.println(casResult); } private int value = 10; } ``` 在上面的代码,首先通过反射获取Unsafe的实例,然后将初始值存储在的某个字段,接着使用`unsafe.compareAndSwapInt()`方法进行CAS操作,将值从10更新为20。这个方法的第一个参数是对象实例,第二个参数是字段的内存地址偏移量,第三个参数是期望值,第四个参数是更新值。如果更新成功,该方法返回true,否则返回false。 需要注意的是,Unsafe使用需要非常小心,因为它可以直接操作内存,容易引发安全问题。在使用Unsafe的时候,应该先了解清楚相关的API文档,并且在实际应用要进行严格的测试和验证。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值