您的位置:首页 > Web前端

java中的unSafe类

2018-09-12 14:23 316 查看

UnSafe的简单介绍

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

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

私有的构造器

静态方法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://blog.csdn.net/ahilll/article/details/81628215

https://www.2cto.com/kf/201604/499613.html

https://www.cnblogs.com/mickole/articles/3757278.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java