Java Unsafe详细解析
简介
Java Unsafe 是 JDK 提供的一个支持直接操作内存、线程、JVM 的类库。由于 Unsafe 操作的是内存,所以它可以绕过 JVM 的安全检查,说白了就是越过了 Java 的限制,直接操作底层内存。不是直接通过 new 实例化对象进行使用,而是通过反射或本地方法调用获取一个实例。
使用
Unsafe 类主要包含以下方法:
-
内存存取操作
- putXXX():存放基本类型到指定的内存地址;
- getXXX():从指定地址获取指定类型的值。
-
对象创建和销毁
- allocateInstance(Class
cls):实例化一个没有调用构造方法的对象; - freeMemory(obj):释放对象空间。
- allocateInstance(Class
-
数组操作
- arrayBaseOffset(Class<?> arrayClass):获取指定类型数组第一个元素的偏移量;
- arrayIndexScale(Class<?> arrayClass):获取指定类型数组每个元素占用的空间大小;
- getObject(Object array, long index):获取指定索引位置的元素;
- putObject(Object array, long index, Object value):设置指定索引位置的元素值。
-
线程和锁操作
- park(boolean isAbsolute, long time):暂停线程运行指定时间,单位为纳秒;
- unpark(Object thread):继续运行指定的线程;
- monitorEnter(Object obj):获取对象锁;
- monitorExit(Object obj):释放对象锁。
示例1:修改String对象值
public class ChangeStringValue {
public static void main(String[] args) throws Exception {
String str = "Hello, World!";
System.out.println("Original string: " + str);
// 获取Unsafe实例
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
// 获取字符数组的首地址和长度,修改数组中某个字符的值
long stringBaseOffset = unsafe.arrayBaseOffset(char[].class);
long addressOfFirstChar = unsafe.getLong(str, stringBaseOffset);
char[] chars = (char[]) unsafe.getObject(str, stringBaseOffset);
chars[7] = 'J';
// 重新构造字符串
String newStr = (String) unsafe.allocateInstance(String.class);
unsafe.putLong(newStr, stringBaseOffset, addressOfFirstChar);
System.out.println("New string: " + newStr);
}
}
运行结果:
Original string: Hello, World!
New string: Hello, Jorld!
此示例展示了可以使用 Unsafe 遍历并修改字符串的字符,以及如何重新构造一个字符串对象。
示例2:破解懒汉式单例模式
public class LazySingleton {
private static volatile LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
通过指针去创建对象的方式可以避免 synchronized 存在的锁消耗,但也会带来多对象被实例化的风险。这种方式并不能保证只会创建一个对象,只是此处概率较小。
public class ReflectionCreateObj implements Serializable {
private static LazySingleton instance;
static {
Field field;
try {
field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
// 获取LazySingleton对象的引用地址
long instanceOffset = unsafe.objectFieldOffset(
ReflectionCreateObj.class.getDeclaredField("instance"));
// 修改对象引用值
UnsafeReflectionTest uninstallObj = new UnsafeReflectionTest();
unsafe.putOrderedObject(uninstallObj, instanceOffset, LazySingleton.getInstance());
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static LazySingleton getInstance() {
return instance;
}
}
此示例展示了通过 Unsafe 修改了 LazySingleton 对象的引用地址,破解了懒汉式单例模式。这种方式可以恶意绕过系统的业务逻辑,窃取并篡改敏感数据。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:java Unsafe详细解析 - Python技术站