unSafe
Java中unSafe类是什么?
- java.util.concurrent包或者java.nio包中经常会看到sun.misc.Unsafe,类如其名,就是不建议使用(不安全、不轻便、不稳定)的、可以用于观察HotSpot JVM内部结构并且可以对其进行修改的一个“后门类”。(有时也可以用来做性能监控和开发工具)
- Java官方不推荐使用,因为很难正确使用,如果使用错误会给JVM带来致命问题。
- 但是很多Java基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如:Netty、Hadoop、Kafaka等。
- Unsafe类在
提升Java运行效率
和增强Java语言底层操作能力
方面作用很大。
如何使用?
-
Unsafe提供了一个私有的静态实例,并且通过检查classloader是否为null来避免java程序直接使用unsafe
//Unsafe源码 private static final Unsafe theUnsafe; @CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if(var0.getClassLoader() != null) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }
-
反射获取Unsafe静态类
/** * 获取Unsafe */ Field f = null; Unsafe unsafe = null; try { f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); unsafe = (Unsafe) f.get(null); } catch (Exception e) { e.printStackTrace(); }
-
通过Unsafe分配使用
堆外内存
。C++中有malloc,realloc和free方法来操作内存。在Unsafe类中对应为:// 分配size字节大小的内存,返回起始地址偏移量 public native long allocateMemory(long size); // 重新给address起始地址的内存分配长度为size字节大小的内存,返回新的内存起始地址偏移量 public native long reallocateMemory(long address, long size); // 释放起始地址为address的内存 public native void freeMemory(long address); // 分配内存方法还有重分配内存方法都是分配的堆外内存,返回的是一个long类型的地址偏移量。这个偏移量在你的Java程序中每块内存都是唯一的。 堆外内存的好处? Java中创建对象都是堆内内存,由JVM管控的Java进程内存,遵循JVM的内存管理机制,通过垃圾回收机制统一管理堆内存。 堆外内存,不受JVM管控,依赖于Unsafe提供的操作堆外内存的native方法。 好处一:减少堆内内存垃圾回收的工作量,减少GC停顿时间。 好处二:提升程序I/O操作性能,通常I/O操作过程中,涉及堆内内存到堆外内存的数据拷贝操作,对于频繁进行内存间数据拷贝且生命周期较短的暂存数据,建议存储到堆外内存。 备注:实际地址=基址+偏移量
-
线程挂起和恢复(通过park和unpack实现)
-
内存屏障(是一类
同步屏障指令
,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作,避免指令重排序) -
实现对象copy(通过Unsafe的 public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7)方法来拷贝)
-
一图胜千言:
应用?
-
AtomicInteger的实现:
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); // 静态字段valueOffset即为字段value的 内存偏移地址 private static final long valueOffset; static { try { // 获取valueOffset valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } // volatile关键字修饰保证数据的可见性、有序性 private volatile int value; ...... public final boolean compareAndSet(int expect, int update) { // 通过字段 valueOffset的值 可以定位到AtomicInteger对象中value的内存地址。可以根据CAS实现对value字段的原子操作。 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } } 备注: 1. CAS保障原子性吗? CAS是通过硬件命令保证了原子性,而i++没有,且硬件级别的原子性比i++这样高级语言的软件级别的运行速度要快得多。虽然CAS也包含了多个操作,但其运算是固定的(就是个比较),这样的锁定性能开销很小。 2. CAS是自旋锁吗? CAS底层实际通过类似于 lock cmpxchgl 这种汇编的指令,通知CPU进行原子性的更新。这其实一个轻量的loc指令,可以让CPU保证原子性的操作,所以说CAS是自旋锁,是有道理的。 3. CAS的自旋无限循环性能问题怎么优化? 分段CAS(分段迁移),比如:LongAdder(当某一个线程如果对一个值更新是,可以看对这个值进行分段更新,每一段叫做一个Cell,在更新每一个Cell的时候,发现说出现了很难更新它的值,出现了多次 CAS失败了,自旋的时候,进行自动迁移段,它会去尝试更新别的分段Cell的值,这样的话就可以让一个线程不会盲目的CAS自旋等待一个更新分段cell的值)。简单理解,就是分段方式,你不行就试试别人,避免盲目死磕浪费时间。 4. ABA问题? 加一个类似于版本号的东西,比如邮戳int stamp之类的。记录更新的次数即可,比较的时候不光比较value也要比较邮戳。
Reference
- https://blog.csdn.net/u011179993/article/details/79360930?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-1.opensearchhbase&spm=1001.2101.3001.4242.2(Java中的Unsafe类)
- https://blog.csdn.net/F_Hello_World/article/details/103467669(Java中的Unsafe类)
- https://www.cnblogs.com/candlia/p/11920170.html(堆外内存与堆内内存详解)
- https://blog.csdn.net/xiaolinzi176/article/details/120753019(Atomic原子类与Unsafe魔法类详解)
- https://blog.csdn.net/liangwenmail/article/details/80832580(CAS操作确保原子性)
- https://baijiahao.baidu.com/s?id=1714420245669778311&wfr=spider&for=pc(JDK成长记17:Atomic类的原理—CAS+valotile)