Unsafe类是Java中位于sun.misc包下的特殊类,它提供了直接操作内存、修改对象字段值等底层能力,其中通过偏移量定位对象字段内存地址的逻辑,是理解其内存操作机制的核心。很多开发者在使用Unsafe类时,会对字段偏移量的获取和应用感到困惑,本文就围绕这一核心点展开解析。

Unsafe类的基础使用与获取方式
Unsafe类无法直接通过构造方法实例化,需要通过其内部的单例方法获取实例,代码示例如下:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeDemo {
private static Unsafe getUnsafe() throws Exception {
// 获取Unsafe类的theUnsafe字段
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
// 设置字段可访问
unsafeField.setAccessible(true);
// 返回单例实例
return (Unsafe) unsafeField.get(null);
}
public static void main(String[] args) throws Exception {
Unsafe unsafe = getUnsafe();
System.out.println("Unsafe实例获取成功");
}
}
对象字段偏移量的获取逻辑
Unsafe类中提供了objectFieldOffset方法,用于获取对象中某个非静态字段的偏移量,这个偏移量是字段相对于对象内存起始地址的字节距离。我们可以通过反射获取字段对象,再调用该方法得到偏移量:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
class User {
private int age;
private String name;
// 静态字段不参与对象实例的偏移量计算
private static int staticCount;
}
public class OffsetDemo {
private static Unsafe getUnsafe() throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
return (Unsafe) unsafeField.get(null);
}
public static void main(String[] args) throws Exception {
Unsafe unsafe = getUnsafe();
// 获取User类的age字段
Field ageField = User.class.getDeclaredField("age");
// 获取age字段的偏移量
long ageOffset = unsafe.objectFieldOffset(ageField);
System.out.println("age字段偏移量:" + ageOffset);
// 获取name字段
Field nameField = User.class.getDeclaredField("name");
long nameOffset = unsafe.objectFieldOffset(nameField);
System.out.println("name字段偏移量:" + nameOffset);
}
}
不同的字段类型、不同的JVM内存布局下,偏移量的数值会有所不同,但同一个类中字段的偏移量相对顺序是固定的,先声明的字段偏移量更小。
通过偏移量直接操作变量内存
得到字段偏移量后,就可以使用Unsafe类的putInt、putObject等方法,直接修改对象对应字段的值,不需要通过反射的set方法,也不需要字段有可访问的setter方法:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
class User {
private int age;
private String name;
}
public class MemoryOperateDemo {
private static Unsafe getUnsafe() throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
return (Unsafe) unsafeField.get(null);
}
public static void main(String[] args) throws Exception {
Unsafe unsafe = getUnsafe();
User user = new User();
// 获取age字段偏移量
Field ageField = User.class.getDeclaredField("age");
long ageOffset = unsafe.objectFieldOffset(ageField);
// 直接修改age字段的值,第一个参数是对象实例,第二个是偏移量,第三个是要设置的值
unsafe.putInt(user, ageOffset, 20);
// 获取name字段偏移量
Field nameField = User.class.getDeclaredField("name");
long nameOffset = unsafe.objectFieldOffset(nameField);
// 直接修改name字段的值
unsafe.putObject(user, nameOffset, "张三");
// 通过get方法验证修改结果
int age = unsafe.getInt(user, ageOffset);
String name = (String) unsafe.getObject(user, nameOffset);
System.out.println("age:" + age + ",name:" + name);
}
}
基于偏移量实现对象复制的核心逻辑
对象复制的本质是将原对象内存中的数据拷贝到新对象对应的内存区域,而偏移量就是定位每个字段内存位置的关键。我们可以通过遍历对象的所有非静态字段,获取每个字段的偏移量和类型,再将原字段的值拷贝到新对象的对应偏移位置,实现浅拷贝:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
class User {
private int age;
private String name;
}
public class ObjectCopyDemo {
private static Unsafe getUnsafe() throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
return (Unsafe) unsafeField.get(null);
}
public static <T> T copyObject(T origin, Class<T> clazz) throws Exception {
Unsafe unsafe = getUnsafe();
// 创建新实例,不调用构造方法
T newObj = (T) unsafe.allocateInstance(clazz);
// 遍历所有声明的字段
for (Field field : clazz.getDeclaredFields()) {
// 跳过静态字段
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
continue;
}
// 获取字段偏移量
long offset = unsafe.objectFieldOffset(field);
// 根据字段类型获取原对象的值并复制到新对象
if (field.getType() == int.class) {
int value = unsafe.getInt(origin, offset);
unsafe.putInt(newObj, offset, value);
} else if (field.getType() == long.class) {
long value = unsafe.getLong(origin, offset);
unsafe.putLong(newObj, offset, value);
} else if (field.getType() == boolean.class) {
boolean value = unsafe.getBoolean(origin, offset);
unsafe.putBoolean(newObj, offset, value);
} else {
// 引用类型字段
Object value = unsafe.getObject(origin, offset);
unsafe.putObject(newObj, offset, value);
}
}
return newObj;
}
public static void main(String[] args) throws Exception {
User originUser = new User();
// 先通过Unsafe修改原对象的字段值
Unsafe unsafe = getUnsafe();
Field ageField = User.class.getDeclaredField("age");
long ageOffset = unsafe.objectFieldOffset(ageField);
unsafe.putInt(originUser, ageOffset, 25);
Field nameField = User.class.getDeclaredField("name");
long nameOffset = unsafe.objectFieldOffset(nameField);
unsafe.putObject(originUser, nameOffset, "李四");
// 复制对象
User copyUser = copyObject(originUser, User.class);
// 验证复制结果
int copyAge = unsafe.getInt(copyUser, ageOffset);
String copyName = (String) unsafe.getObject(copyUser, nameOffset);
System.out.println("复制后age:" + copyAge + ",name:" + copyName);
// 验证两个对象不是同一个实例
System.out.println("是否同一实例:" + (originUser == copyUser));
}
}
注意事项
- Unsafe类的方法都是底层操作,使用不当容易导致内存泄漏、JVM崩溃等问题,生产环境需谨慎使用。
- 偏移量的数值和JVM的内存布局、字段类型等有关,不同环境下可能存在差异,不要硬编码偏移量数值。
- 上述对象复制是浅拷贝,如果字段是引用类型,复制后新对象和原对象的该字段会指向同一个引用对象。
- 从Java 9开始,Unsafe类的部分方法被标记为过期,后续版本可能会有调整,使用时需要注意版本兼容性。