Java中没有指针,不能直接对内存地址的变量进行控制,但Java提供了一个特殊的类Unsafe工具类来间接实现。Unsafe主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用 。正如其名字unsafe,直接去使用这个工具类是不安全的,它能直接在硬件层(内存上)修改访问变量,而无视各种访问修饰符的限制。它几乎所有的公共方法API都是本地方法,这些方法是使用C/C++方法实现的,它越过了虚拟机层面,直接在操作系统本地执行。因为这是一个底层类,如果在不了解其内部原理、未掌握其使用技巧的情况下,我们直接使用Unsafe类可能会造成一些意想不到或未知的错误,所以它被限制开发者直接使用,只能由JDK类库的维护者使用。如果您喜欢阅读JDK的源码,那么你会发现在各种并发工具类的内部常常见到这个类的踪影,它们经常通过这个类的一些方法根据相应内存地址在内存上直接CAS修改访问共享变量的值。
Unsafe类在Oracle的官方JDK中没有提供源码,我们只能通过IDEA的反编译工具看到反编译后的源代码,因此我们看不到方法注释。而只OpenJDK中带有所有JDK的源代码,这里使用OpenJDK作参考讲解材料。以下是OpenJDK中Unsafe的类注释
A collection of methods for performing low-level, unsafe operations. Although the class and all methods are public, use of this class is limited because only trusted code can obtain instances of it.
直译过来大致意思是:此类拥有一组用于执行低级,不安全操作的方法。 尽管此类和所有方法都是公共的,但是由于只有可信代码才能获取该类的实例,因此此类的使用受到限制。
可以看出构造方法被私有化,只能通过静态方法getUnsafe()才能获取此Unsafe单例对象,而此静态方法的使用也是受到限制的,只能由JDK中的其它类来调用,普通开发者使用此方法将抛出异常。
private Unsafe() {} private static final Unsafe theUnsafe = new Unsafe(); @CallerSensitive public static Unsafe getUnsafe() { Class<?> caller = Reflection.getCallerClass(); //调用者Class对象 if (!VM.isSystemDomainLoader(caller.getClassLoader())) //判断调用者的类加载器是否为系统类加载器 //不是JAVA_HOME/jre/lib目录下jar包中的类来调用此方法getUnsafe()就会抛出异常 throw new SecurityException("Unsafe"); return theUnsafe; }
此方法getUnsafe()上的注释也说:
为调用提供执行不安全操作的能力。返回的Unsafe对象应由调用方小心保护,因为它可用于在任意内存地址处读取和写入数据。 绝不能将其传递给不受信任的代码。此类中的大多数方法都是非常底层的,并且对应于少量的硬件指令(在典型的机器上)。 应鼓励编译器相应地优化这些方法,而不是使用Unsafe类来控制。
getUnsafe()要求JDK类库自身调用,当然将开发者可以将自己定义的类放在JDK系统类库中,但这种方式明显是不安全、不方便的,其可行性太低。倘若开发者的确需要使用Unsafe类,我们可以使用反射的方式获取Unsafe实例。
private static Unsafe getUnsafeByReflect() { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); return (Unsafe) f.get(null); } catch (Exception e) { throw new Error(e); } }
使用反射方式,在开发者的classpath中获取到Unsafe实例
package com.aaxis; import java.lang.reflect.Field; import sun.misc.Unsafe; public class Student { private int stuId; private String name; private int age; private static final long STUID_OFFSET; private static final Unsafe UNSAFE = getUnsafeByReflect(); static { try { STUID_OFFSET = UNSAFE.objectFieldOffset(Student.class.getDeclaredField("stuId")); } catch (NoSuchFieldException | SecurityException e) { throw new Error(e); } } private static Unsafe getUnsafeByReflect() { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); return (Unsafe) f.get(null); } catch (Exception e) { throw new Error(e); } } public static void unsafedPrintStuId() { Student student = new Student(34124, "小黄"); int stuId = UNSAFE.getInt(student, STUID_OFFSET); System.out.println(student.getName() + "学号:" + stuId); } public static void main(String[] args) { unsafedPrintStuId(); } //..... }