死磕 java魔法类之Unsafe解析

问题

(1)Unsafe是什么?

(2)Unsafe只有CAS的功能吗?

(3)Unsafe为什么是不安全的?

(4)怎么使用Unsafe?

简介

本章是java并发包专题的第一章,但是第一篇写的却不是java并发包中类,而是java中的魔法类sun.misc.Unsafe。

Unsafe为我们提供了访问底层的机制,这种机制仅供java核心类库使用,而不应该被普通用户使用。

但是,为了更好地了解java的生态体系,我们应该去学习它,去了解它,不求深入到底层的C/C++代码,但求能了解它的基本功能。

获取Unsafe的实例

查看Unsafe的源码我们会发现它提供了一个getUnsafe()的静态方法。

@CallerSensitivepublicstaticUnsafegetUnsafe(){    Class var0 = Reflection.getCallerClass();if(!VM.isSystemDomainLoader(var0.getClassLoader())) {thrownewSecurityException("Unsafe");    }else{returntheUnsafe;    }}

但是,如果直接调用这个方法会抛出一个SecurityException异常,这是因为Unsafe仅供java内部类使用,外部类不应该使用它。

那么,我们就没有方法了吗?

当然不是,我们有反射啊!查看源码,我们发现它有一个属性叫theUnsafe,我们直接通过反射拿到它即可。

publicclassUnsafeTest{publicstaticvoidmain(String[] args) throws NoSuchFieldException, IllegalAccessException{        Field f = Unsafe.class.getDeclaredField("theUnsafe");        f.setAccessible(true);        Unsafeunsafe= (Unsafe) f.get(null);    }}

使用Unsafe实例化一个类

假如我们有一个简单的类如下:

classUser{intage;publicUser(){this.age =10;    }}

如果我们通过构造方法实例化这个类,age属性将会返回10。

User user1 =newUser();// 打印10System.out.println(user1.age);

如果我们调用Unsafe来实例化呢?

User user2 = (User) unsafe.allocateInstance(User.class);// 打印0System.out.println(user2.age);

age将返回0,因为 Unsafe.allocateInstance() 只会给对象分配内存,并不会调用构造方法,所以这里只会返回int类型的默认值0。

修改私有字段的值

使用Unsafe的putXXX()方法,我们可以修改任意私有字段的值。

publicclassUnsafeTest{publicstaticvoidmain(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException{        Field f = Unsafe.class.getDeclaredField("theUnsafe");        f.setAccessible(true);        Unsafeunsafe= (Unsafe) f.get(null);        User user =newUser();        Field age = user.getClass().getDeclaredField("age");unsafe.putInt(user,unsafe.objectFieldOffset(age),20);// 打印20System.out.println(user.getAge());    }}classUser{privateintage;publicUser(){this.age =10;    }publicintgetAge(){returnage;    }}

一旦我们通过反射调用得到字段age,我们就可以使用Unsafe将其值更改为任何其他int值。(当然,这里也可以通过反射直接修改)

抛出checked异常

我们知道如果代码抛出了checked异常,要不就使用try...catch捕获它,要不就在方法签名上定义这个异常,但是,通过Unsafe我们可以抛出一个checked异常,同时却不用捕获或在方法签名上定义它。

// 使用正常方式抛出IOException需要定义在方法签名上往外抛publicstaticvoidreadFile()throwsIOException{thrownewIOException();}// 使用Unsafe抛出异常不需要定义在方法签名上往外抛publicstaticvoidreadFileUnsafe(){    unsafe.throwException(newIOException());}

使用堆外内存

如果进程在运行过程中JVM上的内存不足了,会导致频繁的进行GC。理想情况下,我们可以考虑使用堆外内存,这是一块不受JVM管理的内存。

使用Unsafe的allocateMemory()我们可以直接在堆外分配内存,这可能非常有用,但我们要记住,这个内存不受JVM管理,因此我们要调用freeMemory()方法手动释放它。

假设我们要在堆外创建一个巨大的int数组,我们可以使用allocateMemory()方法来实现:

classOffHeapArray{// 一个int等于4个字节privatestaticfinalintINT =4;privatelongsize;privatelongaddress;privatestaticUnsafeunsafe;static{try{            Field f = Unsafe.class.getDeclaredField("theUnsafe");            f.setAccessible(true);unsafe= (Unsafe) f.get(null);        }catch(NoSuchFieldException e) {            e.printStackTrace();        }catch(IllegalAccessException e) {            e.printStackTrace();        }    }// 构造方法,分配内存publicOffHeapArray(longsize){this.size = size;// 参数字节数address =unsafe.allocateMemory(size * INT);    }// 获取指定索引处的元素publicintget(longi){returnunsafe.getInt(address + i * INT);    }// 设置指定索引处的元素publicvoidset(longi,intvalue){unsafe.putInt(address + i * INT,value);    }// 元素个数publiclongsize(){returnsize;    }// 释放堆外内存publicvoidfreeMemory(){unsafe.freeMemory(address);    }}

在构造方法中调用allocateMemory()分配内存,在使用完成后调用freeMemory()释放内存。

使用方式如下:

OffHeapArray offHeapArray =newOffHeapArray(4);offHeapArray.set(0,1);offHeapArray.set(1,2);offHeapArray.set(2,3);offHeapArray.set(3,4);offHeapArray.set(2,5);// 在索引2的位置重复放入元素intsum =0;for(inti =0; i < offHeapArray.size(); i++) {    sum += offHeapArray.get(i);}// 打印12System.out.println(sum);offHeapArray.freeMemory();

最后,一定要记得调用freeMemory()将内存释放回操作系统。

进群:697699179可以获取Java各类入门学习资料!

这是我的微信公众号【编程study】各位大佬有空可以关注下,每天更新Java学习方法,感谢!

学习中遇到问题有不明白的地方,推荐加小编Java学习群:697699179内有视频教程 ,直播课程 ,等学习资料,期待你的加入

CompareAndSwap操作

JUC下面大量使用了CAS操作,它们的底层是调用的Unsafe的CompareAndSwapXXX()方法。这种方式广泛运用于无锁算法,与java中标准的悲观锁机制相比,它可以利用CAS处理器指令提供极大的加速。

比如,我们可以基于Unsafe的compareAndSwapInt()方法构建线程安全的计数器。

classCounter{privatevolatileintcount =0;privatestaticlongoffset;privatestaticUnsafeunsafe;static{try{            Field f = Unsafe.class.getDeclaredField("theUnsafe");            f.setAccessible(true);unsafe= (Unsafe) f.get(null);            offset =unsafe.objectFieldOffset(Counter.class.getDeclaredField("count"));        }catch(NoSuchFieldException e) {            e.printStackTrace();        }catch(IllegalAccessException e) {            e.printStackTrace();        }    }publicvoidincrement(){intbefore = count;// 失败了就重试直到成功为止while(!unsafe.compareAndSwapInt(this, offset, before, before +1)) {            before = count;        }    }publicintgetCount(){returncount;    }}

我们定义了一个volatile的字段count,以便对它的修改所有线程都可见,并在类加载的时候获取count在类中的偏移地址。

在increment()方法中,我们通过调用Unsafe的compareAndSwapInt()方法来尝试更新之前获取到的count的值,如果它没有被其它线程更新过,则更新成功,否则不断重试直到成功为止。

我们可以通过使用多个线程来测试我们的代码:

Counter counter =newCounter();ExecutorService threadPool = Executors.newFixedThreadPool(100);// 起100个线程,每个线程自增10000次IntStream.range(0,100)    .forEach(i->threadPool.submit(()->IntStream.range(0,10000)        .forEach(j->counter.increment())));threadPool.shutdown();Thread.sleep(2000);// 打印1000000System.out.println(counter.getCount());

park/unpark

JVM在上下文切换的时候使用了Unsafe中的两个非常牛逼的方法park()和unpark()。

当一个线程正在等待某个操作时,JVM调用Unsafe的park()方法来阻塞此线程。

当阻塞中的线程需要再次运行时,JVM调用Unsafe的unpark()方法来唤醒此线程。

我们之前在分析java中的集合时看到了大量的LockSupport.park()/unpark(),它们底层都是调用的Unsafe的这两个方法。

总结

使用Unsafe几乎可以操作一切:

(1)实例化一个类;

(2)修改私有字段的值;

(3)抛出checked异常;

(4)使用堆外内存;

(5)CAS操作;

(6)阻塞/唤醒线程;

彩蛋

论实例化一个类的方式?

(1)通过构造方法实例化一个类;

(2)通过Class实例化一个类;

(3)通过反射实例化一个类;

(4)通过克隆实例化一个类;

(5)通过反序列化实例化一个类;

(6)通过Unsafe实例化一个类;

publicclassInstantialTest {privatestaticUnsafe unsafe;static{try{            Field f = Unsafe.class.getDeclaredField("theUnsafe");            f.setAccessible(true);            unsafe = (Unsafe) f.get(null);        }catch(NoSuchFieldException e) {            e.printStackTrace();        }catch(IllegalAccessException e) {            e.printStackTrace();        }    }publicstaticvoidmain(String[] args)throwsException {// 1. 构造方法User user1 =newUser();// 2. Class,里面实际也是反射User user2 = User.class.newInstance();// 3. 反射User user3 = User.class.getConstructor().newInstance();// 4. 克隆User user4 = (User) user1.clone();// 5. 反序列化User user5 = unserialize(user1);// 6. UnsafeUser user6 = (User) unsafe.allocateInstance(User.class);        System.out.println(user1.age);        System.out.println(user2.age);        System.out.println(user3.age);        System.out.println(user4.age);        System.out.println(user5.age);        System.out.println(user6.age);    }privatestaticUser unserialize(User user1)throwsException {        ObjectOutputStream oos =newObjectOutputStream(newFileOutputStream("D://object.txt"));        oos.writeObject(user1);        oos.close();        ObjectInputStream ois =newObjectInputStream(newFileInputStream("D://object.txt"));// 反序列化User user5 = (User) ois.readObject();        ois.close();returnuser5;    }staticclassUserimplementsCloneable, Serializable {privateintage;publicUser() {this.age =10;        }        @OverrideprotectedObject clone()throwsCloneNotSupportedException {returnsuper.clone();        }    }}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容