UnSafe的使用和分析
最初在接触到UnSafe类是在看JAVA并发性编程源码的时候,在Atomic包的AtomicLong、AtomicInteger、AtomicReference等类中都定义了unsafe的变量。因为此前并没有接触过这个类,因此查阅了一些资料,看了UnSafe类的代码,在这里对相关知识做一个整理,以便日后方便查阅。
1. UnSafe 的作用
众所周知java无法直接访问操作系统的底层,只能通过JNI来访问native层。因此,UnSafe算是java留的一个后门,UnSafe类提供了硬件级别的原子操作,主要提供了以下功能:
-
内存管理 通过UnSafe类可以分配内存,可以扩充内存,可以释放内存等
在UnSafe类中提供了三个native方法allocateMemory、reallocateMemory和freeMemory// 分配内存
public native long allocateMemory(long l);// 扩充内存
public native long reallocateMemory(long l,long ll);// 释放内存
pulic native void freeMemory(long l);// 从一个内存快拷贝到一个内存块
public native void copyMemory(Object srcBase,long scrOffset,Object destBase,long destOffset,long bytes);// 从一个给定的内存地址获取本地指针,如果不是allocateMemory方法的,结果将不确定
public native long getAddress(long address);// 存储一个本地指针到一个给定的内存地址,如果地址不是allocateMemory方法的,结果将不确定
public native void putAddress(long address,long x); -
非常规的对象实例化 allocateInstance()方法提供了另一种创建实例的途径,
//创建一个类的实例 public native Object allocateInstance(Class cls) throw InstantiationException;
-
操作类、对象、变量
// 在给定的内存快中设置值 public native void setMemory(Object o,long offset,long bytes,byte value); // 获取值,不管java的访问权限,其他类似的还有putInt,putLong,putChar等 public native Object getObject(Object 0,long offset); // 设置值,不管java的访问权限,其他类似的还有putInt,putLong,putChar等 public natibe void putObject(Object o,long offset); // 返回给定filed的内存地址偏移量,这个值对于给定的filed是唯一的且是固定不变的 public native long staticFiledOffset(Filed f); // 报告一个给定的字段的位置,不管这个字段是private、public还是保护类型,和staticFiledBase结合使用 public native long objectFieldBase(Field f); // 获取一个给定字段的位置 public native Object staticFieldBase(Filed f);
-
数组操作
// 获取数组第一个元素的偏移地址 public native int arrayBaseOffse(Class arrayClass); // 获取数组的转换因子,也就是数组中元素的增量地址。将arrayBaseOffset与arrayIndexScale配合使用,可以定位数组中每个元素在内存中的位置 public native int arrayIndexScale(Class arrayClass);
-
多线程同步
// 锁定对象,必须是没有被锁的 public native void monitorEnter(Object o); // 解锁对象 public native void monitorEnter(Object o); //试图锁定对象 public native boolean tryMonitorEnter(Object o); //CAS public final native boolean compareAndSwapObject(Object o,long offset,Object expected,Object x);
-
挂起与恢复
一个线程的挂起和终止挂起是调用的park和unpark的方法,在并发的框架中,对线程的挂起操作是封装在LockSupport了只的。先将LockSupport的park和unpark两个方法,先将其代码在下面展示,以后在详细的对该类的代码进行分析。
LockSupport中的park
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
LockSupport中的unpark
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
从上面两段代码,我们可以知道,最后调用的UnSafe里面的park()和unpark()方法。
UnSafe中的park和unpark
// 在线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现
public native void park(Boolean isAbsolute,long time);
// 终止挂起的线程
public native void unpark(Object thread);
==从UnSafe的作用来看,确实是一个不安全的,因此要慎用UnSafe类。==
2. UnSafe的代码分析
- 获取UnSafe实例静态方法
private Unsafe() {}
private static final Unsafe theUnsafe = new Unsafe();
public static Unsafe getUnsafe() {
//得到调用者的类
Class<?> caller = Reflection.getCallerClass();
// 如果不是JVM信任的类加载器,就会抛出SecurityException
if (!VM.isSystemDomainLoader(caller.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
3. UnSafe 的使用
- 获取UnSafe
直接获取unSafe,根据上面对getUnsafe()方法的分析,我们直到,这种方式会报错,现尝试一下。public class testGetUnSafe { private long count; public testGetUnSafe(){ this.count = 1; } public long getValue(){ return this.count; } public static void main(String[] args) throws Exception { System.out.println("=======unSafe的allocateInstance方法获取======"); Field f = Unsafe.class.getDeclaredField( "theUnsafe" ); f.setAccessible( true ); Unsafe firstUnsafe = (Unsafe) f.get( null ); testGetUnSafe firstTgus = (testGetUnSafe) firstUnsafe.allocateInstance( testGetUnSafe.class ); System.out.println( "unSafe的allocateInstance方法:" + firstTgus.getValue() ); System.out.println("====== 直接获取 ======"); Unsafe secondUnsafe = Unsafe.getUnsafe(); testGetUnSafe secondTgus = (testGetUnSafe) secondUnsafe.allocateInstance( testGetUnSafe.class ); System.out.println( "unSafe的allocateInstance方法:" + secondTgus.getValue() ); }
调试的结果
- allocateInstance方法创建实例
public class testGetUnSafe {
private long count;
public testGetUnSafe(){
this.count = 1;
}
public long getValue(){
return this.count;
}
public static void main(String[] args) throws Exception {
// 构造方法获取
testGetUnSafe constructMethod = new testGetUnSafe();
System.out.println("constructor获取:"+constructMethod.getValue());
testGetUnSafe reflectMethod = testGetUnSafe.class.newInstance();
System.out.println("reflection获取:"+reflectMethod.getValue());
Field field = Unsafe.class.getDeclaredField( "theUnsafe" );
field.setAccessible( true );
Unsafe firstUnsafe = (Unsafe) field.get( null );
testGetUnSafe unSafeMethod = (testGetUnSafe) firstUnsafe.allocateInstance( testGetUnSafe.class );
System.out.println( "unSafe的allocateInstance方法获取:" + unSafeMethod.getValue() );
调试的结果
![image.png](https://img.hacpai.com/file/2019/02/image-1ee9e8db.png)
从结果可以看出了,使用unSafe的allocateInstance方法创建一个类的实例对象的额时候,跳过了构造函数。
- 改变内存中的值
在我们学习String的时候,都知道String是不可变的,对String的回收要受到JVM的管理;在安全性方面,我们有时候希望,该字符串使用完成以后需要抹去其值。
private static Object password = "admin";
// 改变String的值
public static void deletePassword() throws Exception{
System.out.println("处理之前的password:" + password.toString() + ";password的hashcode:" + password.hashCode());
Field stringValue = String.class.getDeclaredField( "value" );
stringValue.setAccessible( true );
char[] message = (char[]) stringValue.get( password );
for (int i = 0;i < message.length ; i++){
message[i] = '?';
}
System.out.println("处理之后的password:" + password.toString() + ";password的hashcode:" + password.hashCode());
password = "****";
System.out.println("处理之后的password:" + password.toString() + ";password的hashcode:" + password.hashCode());
}
调试的结果
![image.png](https://img.hacpai.com/file/2019/02/image-74ea1627.png)
从上面的结果,我们可以看出,在平时的使用中,string的值一旦确定就不会在更改了。得到的string的变动是在内存中又开辟了一小段空间来存放该值的,将变量的引用指向了这段内存区域。在获取到权限以后,就可以更改该地址中的值了。