Unsafe使用示例代码(一)

简介: Unsafe使用示例代码(一)

简要介绍:

Java语言先比较与C和C++有一个非常大的不同点在于Java语言无法直接操作内存,实际开发中,默认都是由JVM来进行内存分配和垃圾回收,而JVM在进行垃圾回收的时候,绝大多数垃圾回收器都需要STW(stop the world)这个问题往往会导致服务短暂或者较长时间的暂停。因此Unsafe提供了通过Java直接操作内存的API,尽管Unsafe是JavaNIO和并发的核心类,但是其如其名,这是一个官方不推荐开发者使用的及其不安全的类!

主要作用:

序号

作用

API

1

内存管理。(包括分配内存、释放内存等。)

allocateMemory(分配内存)、reallocateMemory(重新分配内存)、copyMemory(拷贝内存)、freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)

2

非常规的对象实例化

allocateInstance()方法提供了另一种创建实例的途径

3

操作类、对象、变量

staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)

4

数组操作

arrayBaseOffset(获取数组第一个元素的偏移地址)、arrayIndexScale(获取数组中元素的增量地址)等方法

5

多线程同步。包括锁机制、CAS操作等

monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap

6

挂起与恢复

park、unpark

7

内存屏障

loadFence、storeFence、fullFence

一、获取Unsafe

源码-基于jdk1.8

/*

* 在Unsafe源码中限制了获取Unsafe的ClassLoader,如果这个方法调用实例不是由BootStrap类加载器加载的,则会报错

* 因此,我们如果需要使用Unsafe类,可以通过反射的方式来获取。

*/

@CallerSensitive

public static Unsafe getUnsafe() {

   Class var0 = Reflection.getCallerClass();

   // 此处会判断ClassLoader是否为空,BootStrap由C语言编写,在Java中获取会返回null。

   if (!VM.isSystemDomainLoader(var0.getClassLoader())) {

       throw new SecurityException("Unsafe");

   } else {

       return theUnsafe;

   }

}

获取方式

/**

* 反射获取Unsafe

*

* @return Unsafe

*/

public static final Unsafe getUnsafe() {


   Unsafe unsafe = null;

   try {

       Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");

       theUnsafe.setAccessible(true);

       unsafe = (Unsafe) theUnsafe.get(null);

   } catch (NoSuchFieldException | IllegalAccessException e) {

       e.printStackTrace();

   }

   return unsafe;

}

二、操作方法

数组操作

package com.liziba.unsafe;


import sun.misc.Unsafe;


/**

* <p>

*      操作数组示例

* </p>

*

* @Author: Liziba

* @Date: 2021/5/23 13:33

*/

public class OperateArrayExample {



   /**

    *  1、public native int arrayBaseOffset(Class<?> var1); 获取数组第一个元素的偏移地址

    *  2、public native int arrayIndexScale(Class<?> var1); 获取数组中元素的增量地址

    *  3、public Object getObject(Object var1, int var2); 通过对象和地址偏移量获取元素

    */

   public static void operateArrayUseUnsafe() {

       // 测试数组

       String[] exampleArray = new String [] {"李" , "子", "捌"};


       Unsafe unsafe = UnsafeFactory.getUnsafe();

       // 获取数组的基本偏移量

       int baseOffset = unsafe.arrayBaseOffset(String[].class);

       System.out.println("String[] base offset is : " + baseOffset);


       // 获取数组中元素的增量地址

       int scale = unsafe.arrayIndexScale(String[].class);

       System.out.println("String[] index scale is : " + scale);


       // 获取数组中第n个元素 i = (baseOffset + (scale * n-1))

       System.out.println("third element is :" + unsafe.getObject(exampleArray, baseOffset + (scale * 2)));


       // 修改数组中第n个元素 i = (baseOffset + (scale * n-1))

       unsafe.putObject(exampleArray, baseOffset + scale * 2, "柒");

       System.out.println("third element is :" + unsafe.getObject(exampleArray, baseOffset + (scale * 2)));

   }


   public static void main(String[] args) {

       OperateArrayExample.operateArrayUseUnsafe();

   }


}


输出结果

对象操作

package com.liziba.unsafe;


import com.liziba.unsafe.pojo.User;

import sun.misc.Unsafe;


import java.io.File;

import java.io.FileInputStream;

import java.lang.reflect.Constructor;

import java.lang.reflect.Field;


/**

* <p>

*      操作对象示例

* </p>

*

* @Author: Liziba

* @Date: 2021/5/24 20:40

*/

public class OperateObjectExample {


   /**

    *  1、public native Object allocateInstance(Class<?> var1); 分配内存

    *  2、public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6); 方法定义一个类用于动态的创建类

    * @throws Exception

    */

   public static void operateObjectUseUnsafe() throws Exception{


       Unsafe unsafe = UnsafeFactory.getUnsafe();


       // 使用Unsafe的allocateInstance()方法,可以无需使用构造函数的情况下实例化对象

       User user = (User) unsafe.allocateInstance(User.class);

       user.setId(1);

       user.setName("李子捌");

       System.out.println(user);


       // 返回对象成员属性在内存中相对于对象在内存中地址的偏移量

       Field name = User.class.getDeclaredField("name");

       long fieldOffset = unsafe.objectFieldOffset(name);


       // 使用Unsafe的putXxx()方法,可以直接修改内存地址指向的数据(可以越过权限访问控制符)

       unsafe.putObject(user, fieldOffset, "李子柒");

       System.out.println(user);


       // 使用Unsafe在运行时通过.class文件,创建类

       File classFile = new File("E:\\workspaceall\\liziba-javap5\\out\\production\\liziba-javap5\\com\\liziba\\unsafe\\pojo\\User.class");

       FileInputStream fis = new FileInputStream(classFile);

       byte [] classContent = new byte[(int) classFile.length()];

       fis.read(classContent);

       Class<?> clazz = unsafe.defineClass(null, classContent, 0, classContent.length, null, null);

       Constructor<?> constructor = clazz.getDeclaredConstructor(int.class, String.class);

       System.out.println(constructor.newInstance(1, "李子玖"));


   }


   public static void main(String[] args) {

       try {

           OperateObjectExample.operateObjectUseUnsafe();

       } catch (Exception e) {

           e.printStackTrace();

       }

   }


}


输出结果

内存操作

package com.liziba.unsafe;


import sun.misc.Unsafe;


/**

* <p>

*      內存地址操作示例

* </p>

*

* @Author: Liziba

* @Date: 2021/5/24 21:32

*/

public class OperateMemoryExample {


   /**

    * 1、public native long allocateMemory(long var1);  分配var1字节大小的内存,返回起始地址偏移量

    * 2、public native long reallocateMemory(long var1, long var3);  重新给var1起始地址的内存分配长度为var3字节的内存,返回新的内存起始地址偏移量

    * 3、public native void freeMemory(long var1); 释放起始地址为var1的地址

    *

    * 分配地址的方法还有重分配,都是分配在堆外内存,返回的是一个long类型的地址偏移量。这个偏移量在Java程序中的每一块内存都是唯一的

    *

    */

   public static void operateMemoryUseUnsafe() {


       Unsafe unsafe = UnsafeFactory.getUnsafe();

       // 申请分配8byte的内存

       long address = unsafe.allocateMemory(1L);

       // 初始化内存填充值

       unsafe.putByte(address, (byte)1);

       // 测试输出

       System.out.println(new StringBuilder().append("address: ").append(address).append(" byte value: ").append(unsafe.getByte(address)));


       // 重新分配一个地址

       long newAddress = unsafe.reallocateMemory(address, 8L);

       unsafe.putLong(newAddress, 8888L);

       System.out.println(new StringBuilder().append("address: ").append(newAddress).append(" long value: ").append(unsafe.getLong(newAddress)));


       // 释放地址,注意地址可能被其他使用

       unsafe.freeMemory(newAddress);

       System.out.println(new StringBuilder().append("address: ").append(newAddress).append(" long value: ").append(unsafe.getLong(newAddress)));


   }



   public static void main(String[] args) {

       OperateMemoryExample.operateMemoryUseUnsafe();

   }


}


输出结果

CAS操作

package com.liziba.unsafe;


import com.liziba.unsafe.pojo.User;

import sun.misc.Unsafe;


import java.lang.reflect.Field;


/**

* <p>

*      CAS操作示例

* </p>

*

* @Author: Liziba

* @Date: 2021/5/24 22:18

*/

public class OperateCASExample {


   /**

    * CAS == compare and swap(比较并替换)

    * 当需要改变的值为期望值的时候,就替换为新的值,是原子(不可再分割)操作。Java中大量的并发框架底层使用到了CAS操作。

    * 优势:无锁操作,减少线程切换带来的开销

    * 缺点:CAS容易在并发的情况下失败从而引发性能问题,也存在ABA问题。

    *

    * Unsafe中提供了三个方法

    * 1、compareAndSwapInt

    * 2、compareAndSwapLong

    * 3、compareAndSwapObject

    *

    */

   public static void operateCASUseUnsafe() throws Exception {


       User user = new User(1, "李子捌");

       System.out.println("pre user value: " + user);


       Unsafe unsafe = UnsafeFactory.getUnsafe();

       Field id = user.getClass().getDeclaredField("id");

       Field name = user.getClass().getDeclaredField("name");

       // 获取ID字段的内存偏移量

       long idFieldOffset = unsafe.objectFieldOffset(id);

       // 获取name字段的内存偏移量

       long nameFieldOffset = unsafe.objectFieldOffset(name);

       // 如果ID的期望值是1,则修改为18  success

       unsafe.compareAndSwapInt(user, idFieldOffset, 1, 18);

       // 如果name的期望值是小荔枝,则修改为李子柒  fail

       unsafe.compareAndSwapObject(user, nameFieldOffset, "小荔枝", "李子柒");

       // 输出修改的user对象

       System.out.println("post user value: " + user);


   }



   public static void main(String[] args) {

       try {

           OperateCASExample.operateCASUseUnsafe();

       } catch (Exception e) {

           e.printStackTrace();

       }

   }



}


输出结果



线程的挂起和恢复

/**

* 查看Java的java.util.concurrent.locks.LockSupport源代码可以发现LockSupport类

* 中有各种版本的pack方法但是最终都是通过调用Unsafe.park()方法实现的。

*/


public class LockSupport {


public static void unpark(Thread thread) {

       if (thread != null)

           UNSAFE.unpark(thread);

   }

   

   

   public static void park(Object blocker) {

       Thread t = Thread.currentThread();

       setBlocker(t, blocker);

       UNSAFE.park(false, 0L);

       setBlocker(t, null);

   }

   

   

   public static void parkNanos(Object blocker, long nanos) {

       if (nanos > 0) {

           Thread t = Thread.currentThread();

           setBlocker(t, blocker);

           UNSAFE.park(false, nanos);

           setBlocker(t, null);

       }

   }

   

   public static void parkNanos(long nanos) {

       if (nanos > 0)

           UNSAFE.park(false, nanos);

   }

   

   public static void parkUntil(Object blocker, long deadline) {

       Thread t = Thread.currentThread();

       setBlocker(t, blocker);

       UNSAFE.park(true, deadline);

       setBlocker(t, null);

   }

   

    public static void parkUntil(long deadline) {

       UNSAFE.park(true, deadline);

   }

}

目录
相关文章
|
2月前
|
安全 Java
unsafe类和varhandle类讲解
本文介绍了Java中的Unsafe类和VarHandle类,展示了Unsafe类如何通过底层操作绕过Java的安全限制直接访问内存和对象,以及VarHandle类如何在Java 9及以上版本中提供原子性和可变性访问。
29 1
unsafe类和varhandle类讲解
|
4月前
|
存储 Java 编译器
深入理解 go unsafe
深入理解 go unsafe
26 0
|
安全 Go 开发者
Go 语言使用标准库 sync 包的 mutex 互斥锁解决数据静态
Go 语言使用标准库 sync 包的 mutex 互斥锁解决数据静态
53 0
|
存储 安全 编译器
Go语言源码剖析-String和unsafe包
Go语言源码剖析-String和unsafe包
74 0
|
Oracle 前端开发 算法
JDK源码(21)-Unsafe
JDK源码(21)-Unsafe
181 0
JDK源码(21)-Unsafe
|
存储 安全 Go
Go语言:指针和unsafe.Pointer有什么区别? 微客鸟窝
在 Go 语言中,处于安全考虑,是不允许两个指针类型进行转换的,比如 *int 不能转为 *float64。
111 0
Go语言:指针和unsafe.Pointer有什么区别?  微客鸟窝
|
Java
CAS之什么是unsafe类(三)
CAS之什么是unsafe类(三)
210 0
CAS之什么是unsafe类(三)
|
存储 Java Go
go unsafe包的使用
使用unsafe包修改数据
106 0
|
Java
Unsafe使用示例代码(二)
Unsafe使用示例代码(二)
190 1
Unsafe使用示例代码(二)
Java:原子操作类AtomicInteger代码示例
Java:原子操作类AtomicInteger代码示例