Java|Java中的天使和魔鬼:Unsafe类( 二 )


这篇文章是就快速学习下sun.misc.Unsafe的公共API和一些有趣的使用例子 。
1、Unsafe 实例化在使用Unsafe之前我们需要先实例化它 。 但我们不能通过像Unsafe unsafe = new Unsafe()这种简单的方式来实现Unsafe的实例化 , 这是由于Unsafe的构造方法是私有的 。 Unsafe有一个静态的getUnsafe()方法 , 但是如果天真的以为调用该方法就可以的话 , 那你将遇到一个SecurityException异常 , 这是由于该方法只能在被信任的代码中调用 。
那Java是如何判断我们的代码是否是受信的呢?它就是通过判断加载我们代码的类加载器是否是根类加载器 。
我们可是通过这种方法将我们自己的代码变为受信的 , 使用jvm参数bootclasspath 。 如下所示:
但这种方式太难了 , Unsafe类内部有一个名为theUnsafe的私有实例变量 , 我们可以通过反射来获取该实例变量 。
注意: 忽略你的IDE提示. 例如 eclipse可能会报这样的错误”Access restriction…” 单如果你运行你的代码 , 会发现一切正常 。 如果还是还是提示错误 , 你可以通过如下的方式关闭该错误提示:
2、Unsafe API类 sun.misc.Unsafe 由150个方法组成 。 事实上这些方法只有几组是非常重要的用来操作不同的对象 。 下面我们就来看下这些方法中的一部分 。
1、Info 仅仅是返回一个低级别的内存相关的信息
addressSize
pageSize
2、Objects. 提供操作对象和对象字段的方法
allocateInstance
objectFieldOffset
3、Classes. 提供针对类和类的静态字段操作的方法
staticFieldOffset
defineClass
defineAnonymousClass
ensureClassInitialized
4、Arrays. 数组操作
arrayBaseOffset
arrayIndexScale
5、Synchronization. 低级别的同步原语
monitorEnter
tryMonitorEnter
monitorExit
compareAndSwapInt
putOrderedInt
6、Memory. 直接访问内存的方法
allocateMemory
copyMemory
freeMemory
getAddress
getInt
putInt
接下来是一些有趣的使用case
3、跳过构造初始化allocateInstance方法可能是有用的当你需要在构造函数中跳过对象初始化阶段或绕过安全检查又或者你想要实例化哪些没有提供公共构造函数的类时就可以使用该方法 。 考虑下面的类:
通过构造函数 , 反射 , Unsafe分别来实例化该类结果是不同的:
allocateInstance根本没有进入构造方法 , 对于单例模式 , 简直是噩梦 。
4、内存修改 , 绕过安全检查器对C程序员来说这中情况是很常见的 。
思考一下一些简单的类是如何坚持访问规则的:
客户端代码是非常安全的调用giveAccess()检查访问规则 。 不幸的是对所有的客户端代码它总是返回false 。 只有特权用户在某种程度上可以改变ACCESS_ALLOWED常量并且获得访问权限 。
事实上这不是真的 。 这是证明它的代码:
现在所有的客户端都没有访问限制了 。
事实上同样的功能也可以通过反射来实现 。 但有趣的是 通过上面的方式我们修改任何对象 , 即使我们没有持有对象的引用 。
举个例子 在内存中有另外的一个Guard对象 , 并且地址紧挨着当前对象的地址 , 我们就可以通过下面的代码来修改该对象的ACCESS_ALLOWED字段的值 。
注意 , 我们没有使用任何指向该对象的引用 , 16是Guard对象在32位架构上的大小 。 我们也可以通过sizeOf方法来计算Guard对象的大小 。
5、sizeOf 计算内存大小使用objectFieldOffset方法我们可以实现C风格的sizeof方法 。 下面的方法实现返回对象的表面上的大小
算法逻辑如下:收集所有包括父类在内的非静态字段 , 获得每个字段的偏移量 , 发现最大并添加填充 。 也许我错过了一些东西 , 但是概念是明确的 。


推荐阅读