恕我直言,在座的各位都不够资格去用Unsafe

如何实例化?

在使用Unsafe之前我们需要先实例化它。但我们不能通过像Unsafeunsafe=newUnsafe()这种简单的方式来实现Unsafe的实例化,这是由于Unsafe的构造方法是私有的。Unsafe有一个静态的getUnsafe()方法,但是如果天真的以为调用该方法就可以的话,那你将遇到一个SecurityException异常,这是由于该方法只能在被信任的代码中调用。

publicstaticUnsafegetUnsafe(){Classcc=(2);if(()!=null)thrownewSecurityException("Unsafe");returntheUnsafe;}

那Java是如何判断我们的代码是否是受信的呢?它就是通过判断加载我们代码的类加载器是否是根类加载器。

我们可是通过这种方法将我们自己的代码变为受信的,使用jvm参数bootclasspath。如下所示:

java-Xbootclasspath:/usr//jre/lib/:.

但这种方式太难了!

Unsafe类内部有一个名为theUnsafe的私有实例变量,我们可以通过反射来获取该实例变量。

Fieldf=("theUnsafe");(true);Unsafeunsafe=(Unsafe)(null);

注意:忽略你的IDE提示.例如,eclipse可能会报这样的错误”Accessrestriction…”单如果你运行你的代码,会发现一切正常。如果还是还是提示错误,你可以通过如下的方式关闭该错误提示:

Preferences-Java-Compiler-Errors/Warnings-DeprecatedandrestrictedAPI-Forbiddenreference-Warning

有趣的使用案例

1、跳过构造初始化

allocateInstance方法可能是有用的,当你需要在构造函数中跳过对象初始化阶段或绕过安全检查又或者你想要实例化哪些没有提供公共构造函数的类时就可以使用该方法。考虑下面的类:

classA{privatelonga;//notinitializedvaluepublicA(){=1;//initialization}publiclonga(){;}}

通过构造函数,反射,Unsafe分别来实例化该类结果是不同的:

Ao1=newA();//();//prints1Ao2=();//();//prints1Ao3=(A)();//();//prints0

思考一下这些确保对Singletons模式的影响。

2、内存泄露

对C程序员来说这中情况是很常见的。

思考一下一些简单的类是如何坚持访问规则的:

classGuard{privateintACCESS_ALLOWED=1;publicbooleangiveAccess(){return42==ACCESS_ALLOWED;}}

客户端代码是非常安全的,调用giveAccess()检查访问规则。不幸的是对所有的客户端代码,它总是返回false。只有特权用户在某种程度上可以改变ACCESS_ALLOWED常量并且获得访问权限。

事实上,这不是真的。这是证明它的代码:


Guardguard=newGuard();();//false,noaccess//bypassUnsafeunsafe=getUnsafe();Fieldf=().getDeclaredField("ACCESS_ALLOWED");(guard,(f),42);//();//true,accessgranted

现在所有的客户端都没有访问限制了。

事实上同样的功能也可以通过反射来实现。但有趣的是,通过上面的方式我们修改任何对象,即使我们没有持有对象的引用。

举个例子,在内存中有另外的一个Guard对象,并且地址紧挨着当前对象的地址,我们就可以通过下面的代码来修改该对象的ACCESS_ALLOWED字段的值。

(guard,16+(f),42);//memorycorruption

注意,我们没有使用任何指向该对象的引用,16是Guard对象在32位架构上的大小。我们也可以通过sizeOf方法来计算Guard对象的大小。

3、sizeOf

使用objectFieldOffset方法我们可以实现C风格的sizeof方法。下面的方法实现返回对象的表面上的大小。


算法逻辑如下:收集所有包括父类在内的非静态字段,获得每个字段的偏移量,发现最大并添加填充。也许,我错过了一些东西,但是概念是明确的。

更简单的sizeof方法实现逻辑是:我们只读取该对象对应的class对象中关于大小的字段值。在位版本上该表示大小的字段偏移量是12。

publicstaticlongsizeOf(Objectobject){returngetUnsafe().getAddress(normalize(getUnsafe().getInt(object,4L))+12L);}

normalize是一个将有符号的int类型转为无符号的long类型的方法。

privatestaticlongnormalize(intvalue){if(value=0)returnvalue;return(~0L32)value;}

太棒了,这个方法返回的结果和我们之前的sizeof函数是相同的。

butitrequiresspecifyngagentoptioninyourJVM.

事实上,对于合适的,安全的,准确的sizeof函数最好使用包,但它需要特殊的JVM参数。

4、浅拷贝

在实现了计算对象浅层大小的基础上,我们可以非常容易的添加对象的拷贝方法。标准的办法需要修改我们的代码和Cloneable。或者你可以实现自定义的对象拷贝函数,但它不会变为通用的函数。

浅拷贝:

staticObjectshallowCopy(Objectobj){longsize=sizeOf(obj);longstart=toAddress(obj);longaddress=getUnsafe().allocateMemory(size);getUnsafe().copyMemory(start,address,size);returnfromAddress(address);}

toAddress和fromAddress将对象转为它在内存中的地址或者从指定的地址内容转为对象。


该拷贝函数可以用来拷贝任何类型的对象,因为对象的大小是动态计算的。

注意:在完成拷贝动作后你需要将拷贝对象的类型强转为目标类型。

5、隐藏密码

在Unsafe的直接内存访问方法使用case中有一个非常有趣的用法就是删除内存中不想要的对象。

大多数获取用户密码的API方法的返回值不是byte[]就是char[],这是为什么呢?

这完全是出于安全原因,因为我们可以在不需要它们的时候将数组元素置为失效。如果我们获取的密码是字符串类型,则密码字符串是作为一个对象保存在内存中的。要将该密码字符串置为无效,我们只能讲字符串引用职位null,但是该字符串的内容任然存在内存直到GC回收该对象后。

这个技巧在内存创建一个假的大小相同字符串对象来替换原来的:

Stringpassword=newString("l00k@myHor$e");Stringfake=newString((".","?"));(password);//l00k@myHor$(fake);getUnsafe().copyMemory(fake,0L,null,toAddress(password),sizeOf(password));(password);(fake);

感觉安全了吗?

其实该方法不是真的安全。想要真的安全我们可以通过反射API将字符串对象中的字符数组value字段的值修改为null。

FieldstringValue=("value");(true);char[]mem=(char[])(password);for(inti=0;;i++){mem[i]='?';}

动态类

Wecancreateclassesinruntime,ineClassmethod.

我们可以在运行时创建类,例如通过一个编译好的class文件。将class文件的内容读入到字节数组中然后将该数组传递到合适的defineClass方法中。

byte[]classContents=getClassContent();Classc=getUnsafe().defineClass(null,classContents,0,);("a").invoke((),null);//1

读取class文件内如的代码:

privatestaticbyte[]getClassContent()throwsException{Filef=newFile("/home/mishadoff/tmp/");FileInputStreaminput=newFileInputStream(f);byte[]content=newbyte[(int)()];(content);();returncontent;}

该方式是非常有用的,如果你确实需要在运行时动态的创建类。比如生产代理类或切面类。

快速序列化

这种使用方式更实用。

每个人都知道java标准的序列化的功能速度很慢而且它还需要类拥有公有的构造函数。

外部序列化是更好的方式,但是需要定义针对待序列化类的schema。

非常流行的高性能序列化库,像kryo是有使用限制的,比如在内存缺乏的环境就不合适。

但通过使用Unsafe类我们可以非常简单的实现完整的序列化功能。

1、序列化

通过反射定义类的序列化。这个可以只做一次。

通过Unsafe的getLong,getInt,getObject等方法获取字段真实的值。

添加可以恢复该对象的标识符。

将这些数据写入到输出

当然也可以使用压缩来节省空间。

2、反序列化

创建一个序列化类的实例,可以通过方法allocateInstance。因为该方法不需要任何构造方法。

创建schama,和序列化类似

从文件或输入读取或有的字段

使用Unsafe的putLong,putInt,putObject等方法来填充对象。

Actually,therearemuchmoredetailsincorrectinplementation,butintuitionisclear.

事实上要正确实现序列化和反序列化需要注意很多细节,但是思路是清晰的。

这种序列化方式是非常快的。

并发

关于并发编程使用Unsafe的只言片语。compareAndSwap方法是原子的,可以用来实现高性能的无锁化数据结构。

举个例子,多个线程并发的更新共享的对象这种场景。

首先我们定义一个简单的接口Counter:

interfaceCounter{voidincrement();longgetCounter();}

我们定义工作线程CounterClient,它会使用Counter:

classCounterClientimplementsRunnable{privateCounterc;privateintnum;publicCounterClient(Counterc,intnum){=c;=num;}@Overridepublicvoidrun(){for(inti=0;inum;i++){();}}}

这是测试代码:

intNUM_OF_THREADS=1000;intNUM_OF_INCREMENTS=100000;ExecutorServiceservice=(NUM_OF_THREADS);Countercounter=//creatinginstanceofspecificcounterlongbefore=();for(inti=0;iNUM_OF_THREADS;i++){(newCounterClient(counter,NUM_OF_INCREMENTS));}();(1,);longafter=();("Counterresult:"+());("Timepassedinms:"+(after-before));

第一个实现-没有同步的计数器

classStupidCounterimplementsCounter{privatelongcounter=0;@Overridepublicvoidincrement(){counter++;}@OverridepubliclonggetCounter(){returncounter;}}

Output:

Counterresult:99542945Timepassedinms:679

速度很多,但是没有对所有的线程进行协调所以结果是错误的。第二个版本,使用Java常见的同步方式来实现。

classSyncCounterimplementsCounter{privatelongcounter=0;@Overridepublicsynchronizedvoidincrement(){counter++;}@OverridepubliclonggetCounter(){returncounter;}}

Output:

Counterresult:100000000Timepassedinms:10136

彻底的同步当然会导致正确的结果。但是花费的时间令人沮丧。让我们试试ReentrantReadWriteLock:

classLockCounterimplementsCounter{privatelongcounter=0;privateWriteLocklock=newReentrantReadWriteLock().writeLock();@Overridepublicvoidincrement(){();counter++;();}@OverridepubliclonggetCounter(){returncounter;}}

Output:

Counterresult:100000000Timepassedinms:8065

结果依然是正确的,时间也短。那使用原子的类呢?

classAtomicCounterimplementsCounter{AtomicLongcounter=newAtomicLong(0);@Overridepublicvoidincrement(){();}@OverridepubliclonggetCounter(){();}}

Output:

Counterresult:100000000Timepassedinms:6552

使用AtomicCounter的效果更好一点。最后我们试试Unsafe的原子方法compareAndSwapLong看看是不是更进一步。

classCASCounterimplementsCounter{privatevolatilelongcounter=0;privateUnsafeunsafe;privatelongoffset;publicCASCounter()throwsException{unsafe=getUnsafe();offset=(("counter"));}@Overridepublicvoidincrement(){longbefore=counter;while(!(this,offset,before,before+1)){before=counter;}}@OverridepubliclonggetCounter(){returncounter;}}

Output:

Counterresult:100000000Timepassedinms:6454

开起来和使用原子类是一样的效果,难道原子类使用了Unsafe?答案是YES。

事实上该例子非常简单但表现出了Unsafe的强大功能。

就像前面提到的CAS原语可以用来实现高效的无锁数据结构。实现的原理很简单:

拥有一个状态

创建一个它的副本

修改该副本

执行CAS操作

如果失败就重复执行

事实上,在真实的环境它的实现难度超过你的想象,这其中有需要类似ABA,指令重排序这样的问题。

#结论

尽管Unsafe有这么多有用的应用,但是尽量不要使用。当然了使用JDK中利用了Unsafe实现的类是可以的。或者你对你代码功力非常自信。

版权声明:本站所有作品(图文、音视频)均由用户自行上传分享,仅供网友学习交流,不声明或保证其内容的正确性,如发现本站有涉嫌抄袭侵权/违法违规的内容。请举报,一经查实,本站将立刻删除。

相关推荐