UnSafe的使用和分析

  |   0 评论   |   2,608 浏览

最初在接触到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() );
      }
    

    调试的结果
    image.png

  • 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的变动是在内存中又开辟了一小段空间来存放该值的,将变量的引用指向了这段内存区域。在获取到权限以后,就可以更改该地址中的值了。

评论

发表评论


取消