Unsafe类说明
从这个类的名字Unsafe
上来说这个类就是一个不安全的类,也是不开放给用户直接使用的(当然我们还是可以通过其他一些方法用到)。
这个类在jdk源码中多个类中用到,主要作用是任意内存地址位置处读写数据,外加一下CAS操作。它的大部分操作都是绕过JVM通过JNI完成的,因此它所分配的内存需要手动free,所以是非常危险的。但是Unsafe中很多(但不是所有)方法都很有用,且有些情况下,除了使用JNI,没有其他方法弄够完成同样的事情。
至于研究它的起因,是因为我最近在看jdk8的ConcurrentHashMap,这个版本的主要函数就是用过Unsafe来完成的。
Unsafe类的调用
Unsafe类是一个单例,调用的方法为getUnsafe
,如下。可以看到,虽然是可以调用,但是会有一步判断,判断是不是内部会检查该CallerClass是不是由系统类加载器BootstrapClassLoader
加载。由系统类加载器加载的类调用getClassLoader()
会返回null,所以要检查类是否为bootstrap加载器加载只需要检查该方法是不是返回null。
那我们怎么能调用到它呢,有以下2中方法。
- 通过JVM参数-Xbootclasspath指定要使用的类为启动类;
- 在Unsafe类中有一个成员变量
theUnsafe
,因此我们可以通过反射将private单例实例的accessible设置为true,然后通过Field的get方法获取,如下。123Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);
不调用构造方法生成对象
利用Unsafe的allocateInstance方法,可以在未调用构造方法的情况下生成了对象。下面的例子很好的说明了这一点。
内存修改
我们看一下下面的代码,在正常的情况下sizeValidate
始终范围false
,但是通过计算MAX_SIZE
的位移,将其进行修改之后,就会返回true
。
计算Java对象大小
有两种计算Java对象大小的方式。
- 通过java.lang.instrument.Instrumentation的getObjectSize(obj)直接获取对象的大小;
- 通过sun.misc.Unsafe对象的objectFieldOffset(field)等方法结合反射来计算对象的大小。
通过Unsafe获取Java对象大小的基本思路如下:
- 通过反射获得一个类的Field;
- 通过Unsafe的objectFieldOffset()获得每个Field的off。Set;
- 对Field进行遍历,取得最大的offset,然后加上这个field的长度,再加上Padding对齐。
这边不写代码了,想要了解的同学可以看一下这个
Java并发中的应用
在Java并发中会用到CAS操作,对应于Unsafe类中的compareAndSwapInt,compareAndSwapLong等。下面的例子就是使用Unsafe实现的无所数据结构。
看一下Java中AtomicLong
的实现,下面摘出来一部分。可以看到该类在加载的时候将value的偏移位置计算出来,然后在compareAndSet
等方法中使用Unsafe中的CAS操作进行替换,这样的无锁操作可以大大提高效率。
其他使用方式
- 通过Unsafe的
defineClass
可以动态加载Class; - 通过Unsafe的
copyMemory
、freeMemory
等可以实现内存的复制与释放,如果我们知道了对象的大小,利用arrayBaseOffset
和copyMemory
可以完成对象的浅拷贝。
Unsafe还有其他很多的用途,但是要记得这是一个非常危险的类,在使用的过程中需要万分小心,不到万不得已的情况不要使用。