1.背景

  实习的时候一直使用的是Java,由于Java很少直接对内存进行操作,所以总感觉不踏实,就想着如何自己改写内存地址里面的值,修改函数的返回地址等, </br>在排除了使用JNI直接写C代码的方法后,我找到了Unsafe这个类,基本上能满足我想去内存中一日游的想法。   

2.Unsafe类是啥

可以用来在任意内存地址位置(可以写到OS所在的低地址区域吗?)处读写数据,支持CAS原子操作;

3.Unsafe的使用

  Unsafe对象不能直接通过new Unsafe()或调用Unsafe.getUnsafe()获取,原因如下:

  • 不能直接new Unsafe(),原因是Unsafe被设计成单例模式,构造方法是私有的;
  • 不能通过调用Unsafe.getUnsafe()获取,因为getUnsafe被设计成只能从引导类加载器(bootstrap class loader)加载,从getUnsafe的源码中也可以看出来,如下:
@CallerSensitive
   public static Unsafe getUnsafe() {
       //得到调用该方法的Class对象
       Class cc = Reflection.getCallerClass();
       //判断调用该方法的类是否是引导类加载器(bootstrap class loader)
       //如果不是的话,比如由AppClassLoader调用该方法,则抛出SecurityException异常
       if (cc.getClassLoader() != null)
           throw new SecurityException("Unsafe");
       //返回单例对象
       return theUnsafe;
   }

虽然我们不能通过以上方法得到Unsafe对象,但得Unsafe类中有个私有的静态全局属性theUnsafe(Unsafe实例对象),通过反射, </br>可以获取到该成员属性theUnsafe对应的Field对象,并将其设置为可访问,从而得到theUnsafe具体对象,如下:

// 通过反射得到theUnsafe对应的Field对象
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 设置该Field为可访问
field.setAccessible(true);
// 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的
Unsafe unsafe = (Unsafe) field.get(null);

下面举个小栗子,用来生成一个对象实例:

class User {
   private String name = "default";
   private int age = -1;
   
   public User() {
       this.name = "test";
       this.age = 18;
   }
   
   @Override
   public String toString() {
       return name + ": " + age;
   }
}
   
public class Test {
   public static void main(String[] args) throws NoSuchFieldException,
           SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
       Field field = Unsafe.class.getDeclaredField("theUnsafe");
       field.setAccessible(true);
       Unsafe unsafe = (Unsafe) field.get(null);
   
       User user = (User) unsafe.allocateInstance(User.class); //本地方法,功能是生成一个对象实例,但是不会运行该对象的构造方法
       System.out.println(user); //不会执行构造函数:-1
       
       User userFromNormal = new User();
       System.out.println(userFromNormal); //正常实例化一个类执行构造函数后的输出: 18
   
   }
}

4.Unsafe中其他函数

函数:objectFieldOffset, 功能:返回成员属性在内存中的地址相对于对象内存地址的偏移量,比较简单,就是返回成员属性内存地址相对于对象内存地址的偏移量,通过该方法可以计算一个对象在内存中的空间大小,方法是通过反射得到它的所有Field(包括父类继承得到的),找出Field中偏移量最大值,然后对该最大偏移值填充字节数即为对象大小;

函数:putLong,putInt,putDouble,putChar,putObject等(还有put对应的get方法,很简单就是直接读取内存地址处的数据;) 功能:直接修改内存数据(可以越过访问权限) 我们可以举个putLong(Object, long, long)方法详细看下其具体实现,其它的类似,就声明了一个native本地方法:

public native void putLong(Object o, long offset, long x);//o:对象引用 offset:对象内存地址的偏移量 x:写入的数据

举个例子:

class User {
    private String name = "test"; 
    private long id = 1;
    private int age = 2;
    private double height = 1.72;
    

    @Override
    public String toString() {
        return name + "," + id + "," + age + "," + height;
    }
}
public class Test {
    public static void main(String[] args) throws NoSuchFieldException,
            SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);

        User user = new User();
        System.out.println(user); //打印test,1,2,1.72
        
        Class userClass = user.getClass();
        Field name = userClass.getDeclaredField("name");
        Field id = userClass.getDeclaredField("id");
        Field age = userClass.getDeclaredField("age");
        Field height = userClass.getDeclaredField("height");
        //直接往内存地址写数据
        unsafe.putObject(user, unsafe.objectFieldOffset(name), "midified-name");
        unsafe.putLong(user, unsafe.objectFieldOffset(id),100l);
        unsafe.putInt(user, unsafe.objectFieldOffset(age), 101);
        unsafe.putDouble(user, unsafe.objectFieldOffset(height), 100.1);
        
        System.out.println(user);//打印midified-name,100,101,100.1

    }
}    函数:copyMemory、freeMemory    功能:copyMemory:内存数据拷贝,freeMemory:用于释放allocateMemory和reallocateMemory申请的内存

函数:compareAndSwapInt,compareAndSwapLong等 功能:CAS操作的方法

/*
* 将内存值与预期值作比较,判断是否相等,相等的话,写入数据,不相等不做操作,返回旧数据
*/
compareAndSwap (volatile jint *addr, jint old, jint new_val)
{
  jboolean result = false;
  spinlock lock;
  if ((result = (*addr == old)))
    *addr = new_val;
  return result;
}   函数:getLongVolatile/putLongVolatile等方法   功能:这类方法使用volatile语义去存取数据。