### sun.misc.Unsafe
Java最初被设计为一种安全的受控环境。尽管如此,HotSpot还是包含了一个后门sun.misc.Unsafe,提供了一些可以直接操控内存和线程的底层操作。Unsafe被JDK广泛应用于java.nio和并发包等实现中,这个不安全的类提供了一个观察HotSpot JVM内部结构并且可以对其进行修改,但是不建议在生产环境中使用
作用:可以用来在任意内存地址位置处读写数据,支持一些CAS原子操作
### 获取Unsafe实例
Unsafe对象不能直接通过new Unsafe\(\)或调用Unsafe.getUnsafe\(\)获取,原因如下\(访问限制\):
* 不能直接new Unsafe\(\),原因是Unsafe被设计成单例模式,构造方法是私有的;
* 不能通过调用Unsafe.getUnsafe\(\)获取,因为getUnsafe被设计成只能从引导类加载器\(bootstrap class loader\)加载,否则抛出SecurityException异常
```
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
```
获取实例
```
//方法一:我们可以令我们的代码“受信任”。运行程序时,使用bootclasspath选项,指定系统类路径加上你使用的一个Unsafe路径
java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient
// 方法二
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe) field.get(null);
} catch (Exception e) {
}
}
```
注意:忽略你的IDE。比如:eclipse显示”Access restriction…”错误,但如果你运行代码,它将正常运行。如果这个错误提示令人烦恼,可以通过以下设置来避免:
Preferences -> Java -> Compiler -> Errors/Warnings -> Deprecated and restricted API -> Forbidden reference -> Warning
### 重点API
* **allocateInstance\(Class<?> var1\)不调用构造方法生成对象**
```
User instance = (User) UNSAFE.allocateInstance(User.class);
```
* **objectFieldOffset\(Field var1\)返回成员属性在内存中的地址相对于对象内存地址的偏移量**
* **putLong,putInt,putDouble,putChar,putObject等方法,直接修改内存数据(可以越过访问权限)**
```
package com.quancheng;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class CollectionApp {
private static sun.misc.Unsafe UNSAFE;
public static void main(String[] args) {
try {
User instance = (User) UNSAFE.allocateInstance(User.class);
instance.setName("luoyoub");
System.err.println("instance:" + instance);
instance.test();
Field name = instance.getClass().getDeclaredField("name");
UNSAFE.putObject(instance, UNSAFE.objectFieldOffset(name), "huanghui");
instance.test();
} catch (Exception e) {
e.printStackTrace();
}
}
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe) field.get(null);
} catch (Exception e) {
}
}
}
class User {
private String name;
public void setName(String name) {
this.name = name;
}
public void test() {
System.err.println("hello,world" + name);
}
}
```
* copyMemory:内存数据拷贝
* freeMemory:用于释放allocateMemory和reallocateMemory申请的内存
* compareAndSwapInt/compareAndSwapLongCAS操作
* getLongVolatile/putLongVolatile
### 使用场景
* **避免初始化**
当你想要跳过对象初始化阶段,或绕过构造器的安全检查,或实例化一个没有任何公共构造器的类,allocateInstance方法是非常有用的,使用构造器、反射和unsafe初始化它,将得到不同的结果
```
public class CollectionApp {
private static sun.misc.Unsafe UNSAFE;
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
A a = new A();
a.test(); // output ==> 1
A a1 = A.class.newInstance();
a1.test(); // output ==> 1
A instance = (A) UNSAFE.allocateInstance(A.class);
instance.test(); // output ==> 0
}
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe) field.get(null);
} catch (Exception e) {
}
}
}
class A{
private long a;
public A(){
a = 1;
}
public void test(){
System.err.println("a==>" + a);
}
}
```
* **内存崩溃\(Memory corruption\)**
Unsafe可用于绕过安全的常用技术,直接修改内存变量;实际上,反射可以实现相同的功能。但值得关注的是,我们可以修改任何对象,甚至没有这些对象的引用
```
Guard guard = new Guard();
guard.giveAccess(); // false, no access
// bypass
Unsafe unsafe = getUnsafe();
Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruption
guard.giveAccess(); // true, access granted
```
注意:我们不必持有这个对象的引用
* **浅拷贝\(Shallow copy\)**
* **动态类\(Dynamic classes\)**
我们可以在运行时创建一个类,比如从已编译的.class文件中。将类内容读取为字节数组,并正确地传递给defineClass方法;当你必须动态创建类,而现有代码中有一些代理, 这是很有用的
```
private static byte[] getClassContent() throws Exception {
File f = new File("/home/mishadoff/tmp/A.class");
FileInputStream input = new FileInputStream(f);
byte[] content = new byte[(int)f.length()];
input.read(content);
input.close();
return content;
}
byte[] classContents = getClassContent();
Class c = getUnsafe().defineClass(
null, classContents, 0, classContents.length);
c.getMethod("a").invoke(c.newInstance(), null); // 1
```
* **抛出异常\(Throw an Exception\)**
该方法抛出受检异常,但你的代码不必捕捉或重新抛出它,正如运行时异常一样
```
getUnsafe().throwException(new IOException());
```
* **大数组\(Big Arrays\)**
正如你所知,Java数组大小的最大值为Integer.MAX\_VALUE。使用直接内存分配,我们创建的数组大小受限于堆大小;实际上,这是堆外内存(off-heap memory)技术,在java.nio包中部分可用;
这种方式的内存分配不在堆上,且不受GC管理,所以必须小心Unsafe.freeMemory\(\)的使用。它也不执行任何边界检查,所以任何非法访问可能会导致JVM崩溃
```
class SuperArray {
private final static int BYTE = 1;
private long size;
private long address;
public SuperArray(long size) {
this.size = size;
address = getUnsafe().allocateMemory(size * BYTE);
}
public void set(long i, byte value) {
getUnsafe().putByte(address + i * BYTE, value);
}
public int get(long idx) {
return getUnsafe().getByte(address + idx * BYTE);
}
public long size() {
return size;
}
}
long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;
SuperArray array = new SuperArray(SUPER_SIZE);
System.out.println("Array size:" + array.size()); // 4294967294
for (int i = 0; i < 100; i++) {
array.set((long)Integer.MAX_VALUE + i, (byte)3);
sum += array.get((long)Integer.MAX_VALUE + i);
}
System.out.println("Sum of 100 elements:" + sum); // 300
```
* **并发\(Concurrency\)**
几句关于Unsafe的并发性。compareAndSwap方法是原子的,并且可用来实现高性能的、无锁的数据结构
* **挂起与恢复**
定义:
```
public native void unpark(Thread jthread);
public native void park(boolean isAbsolute, long time); // isAbsolute参数是指明时间是绝对的,还是相对的
```
将一个线程进行挂起是通过park方法实现的,调用park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park\(\)方法;
unpark函数为线程提供“许可\(permit\)”,线程调用park函数则等待“许可”。这个有点像信号量,但是这个“许可”是不能叠加的,“许可”是一次性的;比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态,见下例Example1
```
Example1:
// 针对当前线程已经调用过unpark(多次调用unpark的效果和调用一次unpark的效果一样)
public static void main(String[] args) throws InterruptedException {
Thread currThread = Thread.currentThread();
UNSAFE.unpark(currThread);
UNSAFE.unpark(currThread);
UNSAFE.unpark(currThread);
UNSAFE.park(false, 0);
UNSAFE.park(false, 0);
System.out.println("SUCCESS!!!");
}
// 恢复线程interrupt() && UNSAFE.unpark()运行结果一样
public static void main(String[] args) throws InterruptedException {
Thread currThread = Thread.currentThread();
new Thread(()->{
try {
Thread.sleep(3000);
System.err.println("sub thread end");
// currThread.interrupt();
UNSAFE.unpark(currThread);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
UNSAFE.park(false, 0);
System.out.println("SUCCESS!!!");
}
// 如果是相对时间也就是isAbsolute为false(注意这里后面的单位纳秒)到期的时候,与Thread.sleep效果相同,具体有什么区别有待深入研究
//相对时间后面的参数单位是纳秒
UNSAFE.park(false, 3000000000l);
System.out.println("SUCCESS!!!");
long time = System.currentTimeMillis()+3000;
//绝对时间后面的参数单位是毫秒
UNSAFE.park(true, time);
System.out.println("SUCCESS!!!");
```
注意,unpark函数可以先于park调用。比如线程B调用unpark函数,给线程A发了一个“许可”,那么当线程A调用park时,它发现已经有“许可”了,那么它会马上再继续运行。实际上,park函数即使没有“许可”,有时也会无理由地返回,实际上在SUN Jdk中,object.wait\(\)也有可能被假唤醒;
_注意:unpark方法最好不要在调用park前对当前线程调用unpark_
#### Unsafe API
```
sun.misc.Unsafe类包含105个方法。实际上,对各种实体操作有几组重要方法,其中的一些如下:
Info.仅返回一些低级的内存信息
addressSize
pageSize
Objects.提供用于操作对象及其字段的方法
allocateInstance ##直接获取对象实例
objectFieldOffset
Classes.提供用于操作类及其静态字段的方法
staticFieldOffset
defineClass
defineAnonymousClass
ensureClassInitialized
Arrays.操作数组
arrayBaseOffset
arrayIndexScale
Synchronization.低级的同步原语
monitorEnter
tryMonitorEnter
monitorExit
compareAndSwapInt
putOrderedInt
Memory.直接内存访问方法
allocateMemory
copyMemory
freeMemory
getAddress
getInt
putInt
```
### 知识点
* **Unsafe.park\(\)**当遇到线程终止时,会直接返回\(不同于Thread.sleep,Thread.sleep遇到thread.interrupt\(\)会抛异常\)
```
// Thread.sleep会抛异常
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
try {
System.err.println("sub thread start");
Thread.sleep(10000);
System.err.println("sub thread end");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
TimeUnit.SECONDS.sleep(3);
thread.interrupt();
System.out.println("SUCCESS!!!");
}
output==>
sub thread start
SUCCESS!!!
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.quancheng.ConcurrentTest.lambda$main$0(ConcurrentTest.java:13)
at java.lang.Thread.run(Thread.java:745)
Process finished with exit code 0
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
System.err.println("sub thread start");
UNSAFE.park(false,0);
System.err.println("sub thread end");
});
thread.start();
TimeUnit.SECONDS.sleep(3);
UNSAFE.unpark(thread);
System.out.println("SUCCESS!!!");
}
output==>
sub thread start
sub thread end
SUCCESS!!!
Process finished with exit code 0
```
* unpark无法恢复处于sleep中的线程,只能与park配对使用,因为unpark发放的许可只有park能监听到
```
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
System.err.println("sub thread start");
TimeUnit.SECONDS.sleep(10);
System.err.println("sub thread end");
} catch (Exception e) {
e.printStackTrace();
}
});
thread.start();
TimeUnit.SECONDS.sleep(3);
UNSAFE.unpark(thread);
System.out.println("SUCCESS!!!");
}
```
* **park和unpark的灵活之处**
上面已经提到,unpark函数可以先于park调用,这个正是它们的灵活之处。
一个线程它有可能在别的线程unPark之前,或者之后,或者同时调用了park,那么因为park的特性,它可以不用担心自己的park的时序问题,否则,如果park必须要在unpark之前,那么给编程带来很大的麻烦!!
”考虑一下,两个线程同步,要如何处理?
在Java5里是用wait/notify/notifyAll来同步的。wait/notify机制有个很蛋疼的地方是,比如线程B要用notify通知线程A,那么线程B要确保线程A已经在wait调用上等待了,否则线程A可能永远都在等待。编程的时候就会很蛋疼。
另外,是调用notify,还是notifyAll?
notify只会唤醒一个线程,如果错误地有两个线程在同一个对象上wait等待,那么又悲剧了。为了安全起见,貌似只能调用notifyAll了“
_**park/unpark模型真正解耦了线程之间的同步,线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态**_
#### 参考资料
* [http://ifeve.com/sun-misc-unsafe](http://ifeve.com/sun-misc-unsafe/)
* [https://www.jianshu.com/p/a16d638bc921](https://www.jianshu.com/p/a16d638bc921)
* [https://blog.csdn.net/zhxdick/article/details/52003123](https://blog.csdn.net/zhxdick/article/details/52003123)
* [http://www.baeldung.com/java-unsafe](http://www.baeldung.com/java-unsafe)
* [https://blog.csdn.net/hengyunabc/article/details/28126139](https://blog.csdn.net/hengyunabc/article/details/28126139)
- java演变
- JDK各个版本的新特性
- JDK1.5新特性
- JDK1.6新特性
- JDK1.7新特性
- JDK1.8新特性
- JAVA基础
- 面向对象特性
- 多态
- 方法重载
- 方法重写
- class
- 常量
- 访问修饰符
- 类加载路径
- java-equals
- 局部类
- java-hashCode
- Java类初始化顺序
- java-clone方法
- JAVA对象实例化的方法
- 基础部分
- JAVA基础特性
- JAVA关键字
- javabean
- static
- 日期相关
- final
- interface
- 函数式接口
- JAVA异常
- 异常屏蔽
- try-with-resource资源泄露
- JAVA引用
- WeakReference
- SoftReference
- PhantomReference
- 位运算符
- try-with-resource语法糖
- JDK冷知识
- JAVA包装类
- JAVA基本类型与包装类
- java.lang.Boolean
- java.lang.Integer
- java.lang.Byte
- java.lang.Short
- java.lang.Long
- java.lang.Float
- java.lang.Double
- java.lang.Character
- 日期相关
- TemporalAdjusters
- String
- 字符串常量池
- String拼接
- String编译期优化
- StringBuilder&StringBuffer
- intern
- 注解
- java标准注解
- 内置注解
- 元注解
- 自定义注解
- 注解处理器
- JVM注解
- Java8 Annotation新特性
- 反射-Reflective
- Reflection
- Class
- Constructor
- Method
- javabean-property
- MethodHandles
- 泛型
- 类型擦除
- bridge-method
- Accessor&Mutator方法
- enum
- JAVA数组
- finalize方法
- JAR文件
- JAVA高级编程
- CORBA
- JMX
- SPI
- Java SPI使用约定
- ServiceLoader
- 实际应用
- IO
- 工具类
- JDK常用工具类
- Objects
- System
- Optional
- Throwable
- Collections
- Array
- Arrays
- System
- Unsafe
- Number
- ClassLoader
- Runtime
- Object
- Comparator
- VarHandle
- 数据结构
- 栈-Stack
- 队列(Queue)
- Deque
- PriorityQueue
- BlockingQueue
- SynchronousQueue
- ArrayBlockingQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
- ConcurrentLinkedQueue
- 列表
- 迭代器
- KV键值对数据类型
- HashMap
- TreeMap
- Hash冲突
- ConcurrentHashMap
- JDK1.7 ConcurrentHashMap结构
- jdk7&jdk8区别
- 集合
- Vector
- Stack
- HashSet
- TreeSet
- ArrayList
- LinkedList
- ArrayList && LinkedList相互转换
- 线程安全的集合类
- 集合类遍历性能
- 并发容器
- CopyOnWriteArrayList
- ConcurrentHashMap
- 同步容器
- BitMap
- BloomFilter
- SkipList
- 设计模式
- 设计模式六大原则
- 单例模式
- 代理模式
- 静态代理
- 动态代理
- JDK动态代理
- cglib动态代理
- spring aop
- 策略模式
- SpringAOP策略模式的运用
- 生产者消费者模式
- 迭代器模式
- 函数式编程
- 方法引用
- 性能问题
- Lambda
- Lambda类型检查
- Stream
- findFirst和findAny
- reduce
- 原始类型流特化
- 无限流
- 收集器
- 并行流
- AOP
- 静态织入
- aspect
- aspect的定义
- AspectJ与SpringAOP
- 动态织入
- 静态代理
- 动态代理
- JDK动态代理
- CGLib动态代理
- Spring AOP
- SpringAOP五种通知类型
- @Before
- @AfterReturning
- @AfterThrowing
- @After
- @Around
- Aspect优先级
- SpringAOP切点表达式
- within
- execution
- 嵌套调用
- 系统优化与重构
- 重叠构造器模式
- 工具类构造器优化
- 常见面试题
- new Object()到底占用几个字节
- 访问修饰符
- cloneable接口实现原理
- 异常分类以及处理机制
- wait和sleep的区别
- 数组在内存中如何分配
- 类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式
- 类的实例化顺序
- 附录
- JAVA术语
- FAQ
- 墨菲定律
- 康威定律
- 软件设计原则
- 阿姆达尔定律
- 字节码工具
- OSGI