废话
个人理解: java 出现的原因之一, 就是对内存的管理; 在 c/c++, 内存可以随心使用, 超高的性能也伴有极高的风险; java 极大的规避了这种风险, 却也降低了程序运行的性能; 那么 java 是否提供直接操作内存的方法呢? 当然: Unsafe 类就是 java 提供的, 对系统硬件级别的底层操作;
1,Unsafe 的获取方法:
Unsafe 位于 sun.misc 包下, 通常 eclipse 限制了对该类的直接使用, 并且也不能通过 Unsafe 提供的 getUnsafe() 方法获取到该类的实例, 因为你的类不被该类所信任; 具体到源码:
- @CallerSensitive
- public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass();
- if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
- throw new SecurityException("Unsafe");
- } else {
- return theUnsafe;
- }
- }
在方法上有一个 @CallerSensitive 注解, 该注解表示该方法的调用, 需要调用者被该方法信任; 那么怎么获取到 Unsafe 的实例呢? 解决方法如下:
利用反射机制 ,Unsafe 中有一个字段名为 "theUnsafe", 该字段保存有一个 Unsafe 的实例, 只要获取在该字段上的 Unsafe 实例就好了, 代码如下:
- @SuppressWarnings("restriction")
- static private sun.misc.Unsafe getUnsafe() throws IllegalArgumentException, IllegalAccessException {
- Class<?> cls = sun.misc.Unsafe.class;
- Field[] fields = cls.getDeclaredFields();
- for(Field f : fields) {
- if("theUnsafe".equals(f.getName())) {
- f.setAccessible(true);
- return (sun.misc.Unsafe) f.get(null);
- }
- }
- throw new IllegalAccessException("no declared field: theUnsafe");
- }
2,Unsafe 获取对象字段偏移量, 及修改偏移量对应字段的值, 代码如下:
- import java.lang.reflect.Field;public class TestUnsafe {
- static private int number = 5;
- private String c;
- @SuppressWarnings({ "restriction" })
- public static void main(String[] args) throws Throwable {
- TestUnsafe t = new TestUnsafe();
- sun.misc.Unsafe unsafe = getUnsafe();
- // 对象的操作
- //1, 获取对象的字段相对该对象地址的偏移量;
- //1.1 静态字段获取 ; 说明: 静态字段的偏移量相对于该类的内存地址, 即相对于 className.class 返回的对象;
- long staticFieldOffset = unsafe.staticFieldOffset(TestUnsafe.class.getDeclaredField("number"));
- //1.2 非静态字段 ; 说明: 该偏移量相对于该类的实例化对象的内存地址, 即 new 返回的对象; 这里相对于上面实例化的 t 对象
- long unstaticFieldOffset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("c"));
- System.out.println("静态变量相对于类内存地址的偏移量 =" + staticFieldOffset);
- System.out.println("非静态变量相对于实例化对象的偏移量 =" + unstaticFieldOffset);
- // 修改对象字段的值;
- //1.3 修改非基本数据类型的值, 使用: putObject(object , offset , value); 这里修改 实例化对象 t 对应偏移地址字段的值;
- unsafe.putObject(t, unstaticFieldOffset, "b");
- //1.3 修改基本数据类型的值, 使用对应类型的 put 方法, 如: int 使用 putInt(object , offset , value);
- unsafe.putInt(TestUnsafe.class, staticFieldOffset, 4);
- System.out.println("静态变量被修改后的值 =" + TestUnsafe.number);
- System.out.println("非静态变量被修改后的值 =" + t.c);
- }
- // 利用反射获取 Unsafe 的实例
- @SuppressWarnings("restriction")
- static private sun.misc.Unsafe getUnsafe() throws IllegalArgumentException, IllegalAccessException {
- Class<?> cls = sun.misc.Unsafe.class;
- Field[] fields = cls.getDeclaredFields();
- for(Field f : fields) {
- if("theUnsafe".equals(f.getName())) {
- f.setAccessible(true);
- return (sun.misc.Unsafe) f.get(null);
- }
- }
- throw new IllegalAccessException("no declared field: theUnsafe");
- }
- }
3,Unsafe 内存的使用: 申请 allocateMemory(long), 扩展 reallocateMemory(long,long), 销毁 freeMemory(long), 插入值 putXXX(), 获取值 getXXX(), 示例代码如下:
- // 内存使用
- // 说明: 该内存的使用将直接脱离 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(Hello.class, address+2, new Hello());
- //2.3 从给定的内存地址中取出值, 同存入方法基本类似, 基本数据类型使用 getXX(地址) ,object 类型使用 getObject(类类型, 地址);
- byte b = unsafe.getByte(address);
- System.out.println(b);
- //2.3 获取 object 类型值
- Hello h = (Hello) unsafe.getObject(Hello.class, address+2);
- System.out.println(h);
- //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);
- Hello l = new Hello("l");
- Hello l1 = new Hello("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(Hello.class, taddress, new Hello("33"));
- System.out.println(unsafe.getObject(Hello.class, taddress));
- unsafe.freeMemory(taddress);
重要的事情说 n 遍::::Unsafe 申请的内存的使用将直接脱离 jvm,gc 将无法管理 Unsafe 申请的内存, 所以使用之后一定要手动释放内存, 避免内存溢出!!!
4,CAS 操作 (CAS,compare and swap 的缩写, 意: 比较和交换): 硬件级别的原子性更新变量; 在 Unsafe 中主要有三个方法: CompareAndSwapInt() ,CompareAndSwapLong() ,CompareAndSwapObject(); 具体操作, 代码如下:
- //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);
5, 线程挂起和恢复, part(),unpart(), 代码如下:
- //4.1 unsafe 提供线程挂起和恢复的原语;
- /* 挂起线程, 方法如下
- * part(boolean abs,long timeout)
- * 方法说明: 将当前线程挂起, 直到当期时间 "到达"(1)timeout 描述的时间点, 或者等待线程中断或 unpark;
- * (1): 注意: 这里使用的是到达, 即给定的 timeout 时间是一个时间点, 该时间点从 1970 计数开始;
- * 参数说明:
- * abs 为 false 时, 表示 timeout 以纳秒为单位 ; 当为 false 是, 可设置 timeout 为 0, 表示永远挂起, 直到 interrupt 或则 unpart
- * abs 为 true 时, 表示 timeout 以毫秒为单位; 注意, 经测试在 abs 为 true 时, 将 timeout 设置为 0, 线程会立即返回;
- * timeout : 指定线程挂起到某个时间点, 该时间点从 1970 计数开始;
- */
- //ex1 :
- Thread thread = new Thread(()->{
- unsafe.park(false, 0);// 永远挂起
- });
- thread.start();
- /*
- * 4.2 恢复线程, 方法如下:
- * unpark(Object thread);
- * 方法说明: 给与传入对象一个运行的许可, 即将给定的线程从挂起状态恢复到运行状态;
- * 参数说明: thread : 通常是一个线程对象;
- * 特殊说明: unpark 可以在 park 之前使用, 但不论在 park 方法之前, 进行了多少次的调用 unpark 方法, 对于作为参数的 thread 线程始终将只获得一个运行许可;
- * 即: 当 park 方法调用时, 检测到该线程存在一个运行许可, park 方法也会立即返回; 这种方式在多线程中虽然很灵活, 相对于 notify/wait 的方式, 但不建议如此使用;
- */
- //ex2:
- unsafe.unpark(thread);// 恢复线程
来源: https://www.cnblogs.com/loveyoumi/p/9467078.html