Java中邪恶的Unsafe,一半天使一半魔鬼-刘宇
作者:刘宇
CSDN博客地址:https://blog.csdn.net/liuyu973971883
有部分资料参考,如有侵权,请联系删除。如有不正确的地方,烦请指正,谢谢。
一、什么是Unsafe
Java和C++语言的一个重要区别就是Java中我们无法直接操作一块内存区域,不能像C++中那样可以自己申请内存和释放内存。Java中的Unsafe类为我们提供了类似C++手动管理内存的能力。
Unsafe类,全限定名是sun.misc.Unsafe,从名字中我们可以看出来这个类对普通程序员来说是“危险”的,一般应用开发者不会用到这个类。Unsafe类是"final"的,不允许继承,且构造函数是private的,因此我们无法在外部对Unsafe进行实例化。
二、获取Unsafe
虽然Unsafe是无法被实例化的,但是我们可以通过反射获取到。
private static Unsafe getUnsafe(){
Field field = null;
Unsafe unsafe = null;
try {
field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return unsafe;
}
三、Unsafe的功能汇总
1、内存操作
- 这部分主要包含堆外内存的分配、拷贝、释放、给定地址值操作等方法。
// 分配内存, 相当于C++的malloc函数
public native long allocateMemory(long bytes);
// 扩充内存
public native long reallocateMemory(long address, long bytes);
// 释放内存
public native void freeMemory(long address);
// 在给定的内存块中设置值
public native void setMemory(Object o, long offset, long bytes, byte value);
// 内存拷贝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
// 获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
// 为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);
// 获取给定地址的byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果为确定的)
public native byte getByte(long address);
// 为给定地址设置byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果才是确定的)
public native void putByte(long address, byte x);
2、CAS相关
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);
public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);
3、线程调度
- 这部分,包括线程挂起、恢复、锁机制等方法
//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o);
//释放对象锁
@Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);
4、Class相关
- 此部分主要提供Class和它的静态字段的操作相关方法,包含静态字段内存定位、定义类、定义匿名类、检验&确保初始化等。
//获取给定静态字段的内存地址偏移量,这个值对于给定的字段是唯一且固定不变的
public native long staticFieldOffset(Field f);
//获取一个静态类中给定字段的对象指针
public native Object staticFieldBase(Field f);
//判断是否需要初始化一个类,通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。 当且仅当ensureClassInitialized方法不生效时返回false。
public native boolean shouldBeInitialized(Class<?> c);
//检测给定的类是否已经初始化。通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。
public native void ensureClassInitialized(Class<?> c);
//定义一个类,此方法会跳过JVM的所有安全检查,默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例来源于调用者
public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
//定义一个匿名类
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);
5、对象操作
- 主要包含对象成员属性相关操作及非常规的对象实例化方式等相关方法。
// 返回对象成员属性在内存地址相对于此对象的内存地址的偏移量
public native long objectFieldOffset(Field f);
// 获得给定对象的指定地址偏移量的值,与此类似操作还有:getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
// 给定对象的指定地址偏移量设值,与此类似操作还有:putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);
// 从对象的指定偏移量处获取变量的引用,使用volatile的加载语义
public native Object getObjectVolatile(Object o, long offset);
// 存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义
public native void putObjectVolatile(Object o, long offset, Object x);
// 有序、延迟版本的putObjectVolatile方法,不保证值的改变被其他线程立即看到。只有在field被volatile修饰符修饰时有效
public native void putOrderedObject(Object o, long offset, Object x);
// 绕过构造方法、初始化代码来创建对象
public native Object allocateInstance(Class<?> cls) throws InstantiationException;
6、内存屏障
- 在Java 8中引入,用于定义内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。
// 内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
// 内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
public native void storeFence();
// 内存屏障,禁止load、store操作重排序
public native void fullFence();
7、系统相关
// 返回系统指针的大小。返回值为4(32位系统)或 8(64位系统)。
public native int addressSize();
// 内存页的大小,此值为2的幂次方。
public native int pageSize();
四、邪恶的Unsafe
1、通过unsafe绕过构造函数
虽然unsafe可以绕过对象的构造函数,但不能绕过static代码块
package com.test.unsafe;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.Set;
public class UnsafeFooTest {
public static void main(String[] args) throws InstantiationException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Unsafe unsafe = getUnsafe();
//通过unsafe绕过构造函数,但不能绕过static代码块
Simple simple = (Simple)unsafe.allocateInstance(Simple.class);
System.out.println(simple.get());
}
static class Simple{
private long i = 0;
static{
System.out.println("静态代码块被执行");
}
public Simple() {
i=1;
System.out.println("构造函数被执行");
}
public long get(){
return i;
}
}
//获取Unsafe实例
private static Unsafe getUnsafe(){
Field field = null;
Unsafe unsafe = null;
try {
field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return unsafe;
}
}
输出结果:
静态代码块被执行
0
2、通过unsafe修改对象中的属性
package com.test.unsafe;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.Set;
public class UnsafeFooTest {
public static void main(String[] args) throws InstantiationException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Unsafe unsafe = getUnsafe();
//通过unsafe修改对象中的属性
Guard guard = new Guard();
//获取需要修改的属性
Field field = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
//参数1:修改的对象;参数2:需要修改的属性;参数3:修改的值
unsafe.putInt(guard,unsafe.objectFieldOffset(field),88);
guard.work();
}
static class Guard{
private int ACCESS_ALLOWED = 1;
private void work(){
if (ACCESS_ALLOWED == 88){
System.out.println("I am working...");
}
}
}
//获取Unsafe实例
private static Unsafe getUnsafe(){
Field field = null;
Unsafe unsafe = null;
try {
field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return unsafe;
}
}
输出结果:
I am working...
3、通过unsafe加载类
UnsafeFooTest.java
package com.test.unsafe;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.Set;
public class UnsafeFooTest {
public static void main(String[] args) throws InstantiationException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Unsafe unsafe = getUnsafe();
//通过unsafe加载类,他可以跳过JVM校验
File file = new File("E:\\classloader1\\com\\test\\unsafe\\MyObject.class");
//读取类字节
byte[] bytes = loadClassBytes(file);
Class<?> aClass = unsafe.defineClass(null, bytes, 0, bytes.length, UnsafeFooTest.class.getClassLoader(), null);
String result = (String) aClass.getMethod("hello").invoke(aClass.newInstance(),null);
System.out.println(result);
}
private static byte[] loadClassBytes(File classFile){
try(FileInputStream fls = new FileInputStream(classFile);
ByteArrayOutputStream bos = new ByteArrayOutputStream();){
byte[] buffer = new byte[1024];
int len;
while((len=fls.read(buffer))!=-1){
bos.write(buffer,0,len);
}
bos.flush();
return bos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//获取Unsafe实例
private static Unsafe getUnsafe(){
Field field = null;
Unsafe unsafe = null;
try {
field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return unsafe;
}
}
MyObject .java
- 编译后将当前目前的class文件赋值到E盘下,我们刚刚指定的目录中,然后删除原目录class
package com.test.unsafe;
public class MyObject {
static {
System.out.println("My object static block");
}
public MyObject(){
System.out.println("init");
}
public String hello(){
return "Hello World";
}
}
输出结果:
My object static block
init
Hello World
五、Lock、synchronized、Atomic、Unsafe在多线程下的性能比较
1、无任何锁时
package com.test.unsafe;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class UnsafeTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
//定义线程池
ExecutorService executorService = Executors.newFixedThreadPool(1000);
Counter counter = new StupidCounter();
//记录开始时间
long start = System.currentTimeMillis();
//提交1000个任务
for (int i=0;i<1000;i++){
executorService.submit(new CounterRunnable(counter,10000));
}
//关闭线程池
executorService.shutdown();
//等待线程池中任务全部完成,最多1个小时
executorService.awaitTermination(1, TimeUnit.HOURS);
long end = System.currentTimeMillis();
System.out.println("Counter result:"+ counter.getCounter());
System.out.println("Time passed in ms:"+ (end-start));
}
interface Counter{
void increnment();
long getCounter();
}
static class StupidCounter implements Counter{
private long counter = 0;
public void increnment(){
counter++;
}
public long getCounter(){
return counter;
}
}
static class CounterRunnable implements Runnable{
private final Counter counter;
private final int num;
public CounterRunnable(Counter counter, int num) {
this.counter = counter;
this.num = num;
}
@Override
public void run() {
for (int i=0;i<num;i++){
counter.increnment();
}
}
}
}
输出结果:
- 虽然速度很快,只有210毫秒,但是自增结果错误
Counter result:9989619
Time passed in ms:210
2、加上synchronized锁时
package com.test.unsafe;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class UnsafeTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
//定义线程池
ExecutorService executorService = Executors.newFixedThreadPool(1000);
Counter counter = new SyncCounter();
//记录开始时间
long start = System.currentTimeMillis();
//提交1000个任务
for (int i=0;i<1000;i++){
executorService.submit(new CounterRunnable(counter,10000));
}
//关闭线程池
executorService.shutdown();
//等待线程池中任务全部完成,最多1个小时
executorService.awaitTermination(1, TimeUnit.HOURS);
long end = System.currentTimeMillis();
System.out.println("Counter result:"+ counter.getCounter());
System.out.println("Time passed in ms:"+ (end-start));
}
interface Counter{
void increnment();
long getCounter();
}
static class SyncCounter implements Counter{
private long counter = 0;
public synchronized void increnment(){
counter++;
}
public long getCounter(){
return counter;
}
}
static class CounterRunnable implements Runnable{
private final Counter counter;
private final int num;
public CounterRunnable(Counter counter, int num) {
this.counter = counter;
this.num = num;
}
@Override
public void run() {
for (int i=0;i<num;i++){
counter.increnment();
}
}
}
}
输出结果:
- 我们可以看到输出的结果正确,但是时间相对来说就很慢了。
Counter result:10000000
Time passed in ms:538
3、加上lock锁时
package com.test.unsafe;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class UnsafeTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
//定义线程池
ExecutorService executorService = Executors.newFixedThreadPool(1000);
Counter counter = new LockCounter();
//记录开始时间
long start = System.currentTimeMillis();
//提交1000个任务
for (int i=0;i<1000;i++){
executorService.submit(new CounterRunnable(counter,10000));
}
//关闭线程池
executorService.shutdown();
//等待线程池中任务全部完成,最多1个小时
executorService.awaitTermination(1, TimeUnit.HOURS);
long end = System.currentTimeMillis();
System.out.println("Counter result:"+ counter.getCounter());
System.out.println("Time passed in ms:"+ (end-start));
}
interface Counter{
void increnment();
long getCounter();
}
static class LockCounter implements Counter{
private long counter = 0;
private final Lock lock = new ReentrantLock();
public void increnment(){
try{
lock.lock();
counter++;
}finally {
lock.unlock();
}
}
public long getCounter(){
return counter;
}
}
static class CounterRunnable implements Runnable{
private final Counter counter;
private final int num;
public CounterRunnable(Counter counter, int num) {
this.counter = counter;
this.num = num;
}
@Override
public void run() {
for (int i=0;i<num;i++){
counter.increnment();
}
}
}
}
输出结果:
- 我们可以看到输出的结果正确,相对synchronized快了很多。
Counter result:10000000
Time passed in ms:466
4、加上Atomic原子类型时
package com.test.unsafe;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class UnsafeTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
//定义线程池
ExecutorService executorService = Executors.newFixedThreadPool(1000);
Counter counter = new AtomicCounter();
//记录开始时间
long start = System.currentTimeMillis();
//提交1000个任务
for (int i=0;i<1000;i++){
executorService.submit(new CounterRunnable(counter,10000));
}
//关闭线程池
executorService.shutdown();
//等待线程池中任务全部完成,最多1个小时
executorService.awaitTermination(1, TimeUnit.HOURS);
long end = System.currentTimeMillis();
System.out.println("Counter result:"+ counter.getCounter());
System.out.println("Time passed in ms:"+ (end-start));
}
interface Counter{
void increnment();
long getCounter();
}
static class AtomicCounter implements Counter{
private AtomicLong counter = new AtomicLong();
public void increnment(){
counter.getAndIncrement();
}
public long getCounter(){
return counter.get();
}
}
static class CounterRunnable implements Runnable{
private final Counter counter;
private final int num;
public CounterRunnable(Counter counter, int num) {
this.counter = counter;
this.num = num;
}
@Override
public void run() {
for (int i=0;i<num;i++){
counter.increnment();
}
}
}
}
输出结果:
- 我们可以看到输出的结果正确。
Counter result:10000000
Time passed in ms:397
5、通过Unsafe自定义CAS算法
package com.test.unsafe;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class UnsafeTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
//定义线程池
ExecutorService executorService = Executors.newFixedThreadPool(1000);
Counter counter = new CasCounter();
//记录开始时间
long start = System.currentTimeMillis();
//提交1000个任务
for (int i=0;i<1000;i++){
executorService.submit(new CounterRunnable(counter,10000));
}
//关闭线程池
executorService.shutdown();
//等待线程池中任务全部完成,最多1个小时
executorService.awaitTermination(1, TimeUnit.HOURS);
long end = System.currentTimeMillis();
System.out.println("Counter result:"+ counter.getCounter());
System.out.println("Time passed in ms:"+ (end-start));
}
//获取Unsafe实例
private static Unsafe getUnsafe(){
Field field = null;
Unsafe unsafe = null;
try {
field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return unsafe;
}
interface Counter{
void increnment();
long getCounter();
}
static class CasCounter implements Counter{
private volatile long counter = 0;
private Unsafe unsafe;
private long offset;
public CasCounter() throws NoSuchFieldException {
this.unsafe = getUnsafe();
//根据自身类的变量获取偏移量
this.offset = unsafe.objectFieldOffset(CasCounter.class.getDeclaredField("counter"));
}
public void increnment(){
long current = counter;
//进行CAS算法比较
while(!unsafe.compareAndSwapLong(this,offset,current,current+1)){
current = counter;
}
}
public long getCounter(){
return counter;
}
}
static class CounterRunnable implements Runnable{
private final Counter counter;
private final int num;
public CounterRunnable(Counter counter, int num) {
this.counter = counter;
this.num = num;
}
@Override
public void run() {
for (int i=0;i<num;i++){
counter.increnment();
}
}
}
}
输出结果:
- 可以看出自增结果正确,耗时992毫秒
Counter result:10000000
Time passed in ms:992
6、性能比较
我们可以看到,在无任何锁的情况下,速度很快,但是无法保证结果正确性。synchronized锁是一种相对来说比较重的锁,所以他的执行时间相对较长。lock和原子类型Atomic速度最快,而我们自己用Unsafe实现的最慢,并不是Unsafe问题,因为在lock和Atomic中使用的就是Unsafe,只是我们自定义未进行优化而已。