Unsafe 这个类是你用就觉得好用的类。今天我借助它,边介绍它的使用场景便给大家讲实现。
使用场景一:Unsafe 可以用在避免类初始化的场景。也就是不需要执行类构造函数的场景,跳过对象初始化阶段,或绕过构造器的安全检查,或实例化一个没有任何公共构造器的类。看下面的代码:
class Xttblog {
private long xttblog; // not initialized value
public Xttblog() {
this.xttblog = 1; // initialization
}
public long xttblog() {
return this.xttblog;
}
}
下面分别使用构造器、反射和 Unsafe 初始化它,看看最终的结果。
Xttblog obj1 = new Xttblog(); // constructor
obj1.xttblog(); // prints 1
Xttblog obj2 = Xttblog.class.newInstance(); // reflection
obj2.xttblog(); // prints 1
Xttblog obj3 = (Xttblog) unsafe.allocateInstance(Xttblog.class); // unsafe
obj3.xttblog(); // prints 0
运行结果表明,通过 Unsafe 初始化的类,能跳过构造函数。
使用场景二:不通过反射来改变某些访问规则。比如绕过安全的常用技术等,黑客常用的伎俩。模拟代码如下:
class Access {
private int ACCESS_ALLOWED = 66;
public boolean giveAccess() {
return 88 == ACCESS_ALLOWED;
}
}
正常情况下,我们调用 giveAccess 方法,返回的一定是 false。现在,我们通过 Unsafe 让它返回 true。
Access xttblog = new Access();
xttblog.giveAccess(); // false
Unsafe unsafe = getUnsafe();
Field f = xttblog.getClass().getDeclaredField("ACCESS_ALLOWED");
unsafe.putInt(xttblog, unsafe.objectFieldOffset(f), 88);
xttblog.giveAccess(); // true
这样做之后 ACCESS_ALLOWED 的值就彻底的变成 88 了,后面所有的访问都会是 true,当然你还可以再把值改过来。
使用场景三:浅拷贝。普通的做法是使用 Cloneable。但是如果有太多的类,每个类都用 Cloneable 就太麻烦,这时就可以借助 Unsafe 来实现。
static Object shallowCopy(Object obj) {
long size = sizeOf(obj);
long start = toAddress(obj);
long address = getUnsafe().allocateMemory(size);
getUnsafe().copyMemory(start, address, size);
return fromAddress(address);
}
static long toAddress(Object obj) {
Object[] array = new Object[] {obj};
long baseOffset = getUnsafe().arrayBaseOffset(Object[].class);
return normalize(getUnsafe().getInt(array, baseOffset));
}
static Object fromAddress(long address) {
Object[] array = new Object[] {null};
long baseOffset = getUnsafe().arrayBaseOffset(Object[].class);
getUnsafe().putLong(array, baseOffset, address);
return array[0];
}
使用场景四:破解某些密码,获取内存中的密码。通过 Unsafe 可以访问到某些没有被回收的内存。
检索用户密码的大多数 API 的签名为 byte[] 或 char[],为什么是数组呢?这是出于安全的缘故,因为我们可以删除不需要的数组元素。如果将用户密码检索成字符串,这可以像一个对象一样在内存中保存,而删除该对象只需执行解除引用的操作。但是,这个对象仍然在内存中,由 GC 决定的时间来执行清除。
String password = new String("www.xttblog.com");
String fake = new String(password.replaceAll(".", "?"));
System.out.println(password); // www.xttblog.com
System.out.println(fake); // ????????????
getUnsafe().copyMemory(fake,
0L, null, toAddress(password), sizeOf(password));
System.out.println(password); // ????????????
System.out.println(fake); // ????????????
Field stringValue = String.class.getDeclaredField("value");
stringValue.setAccessible(true);
char[] mem = (char[]) stringValue.get(password);
for (int i=0; i < mem.length; i++) {
mem[i] = '?';
}
使用场景五:Java 多继承。Java 中虽然没有多继承,但是我们可以通过 Unsafe 来破坏这个规则。
long intClassAddress = normalize(getUnsafe().getInt(new Integer(0), 4L));
long strClassAddress = normalize(getUnsafe().getInt("", 4L));
getUnsafe().putAddress(intClassAddress + 36, strClassAddress);
(String) (Object) (new Integer(666))
这个代码片段将 String 类型添加到 Integer 超类中,因此我们可以强制转换,且没有运行时异常。但是我们必须预先强制转换对象,以欺骗编译器。
使用场景六:动态类。从已编译的 .class 文件中。将类内容读取为字节数组,并正确地传递给 defineClass 方法。
byte[] classContents = getClassContent();
Class c = getUnsafe().defineClass(
null, classContents, 0, classContents.length);
c.getMethod("xttblog").invoke(c.newInstance(), null); // 1
private static byte[] getClassContent() throws Exception {
File f = new File("/www/xttblog/newclass/Xttblog.class");
FileInputStream input = new FileInputStream(f);
byte[] content = new byte[(int)f.length()];
input.read(content);
input.close();
return content;
}
使用场景七:抛出异常。
getUnsafe().throwException(new IOException());
该方法抛出受检异常,但你的代码不必捕捉或重新抛出它,正如运行时异常一样。
使用场景八:快速序列化。
标准 Java 的 Serializable 的序列化能力是非常慢的。它同时要求类必须有一个公共的、无参数的构造器。Externalizable 比较好,但它需要定义类序列化的模式。流行的高性能库,比如 kryo 具有依赖性,这对于低内存要求来说是不可接受的。
这个时候 unsafe 类就可以派上用场,大显身手了。Gson、Fastjson、commons-collections 等中都有使用,我就不贴代码了。
使用场景九:超越 Integer.MAX_VALUE 限制的大数组。
class SuperArray {
private final static int BYTE = 1;
private long size;
private long address;
public SuperArray(long size) {
this.size = size;
address = getUnsafe().allocateMemory(size * BYTE);
}
public void set(long i, byte value) {
getUnsafe().putByte(address + i * BYTE, value);
}
public int get(long idx) {
return getUnsafe().getByte(address + idx * BYTE);
}
public long size() {
return size;
}
}
class SuperArray {
private final static int BYTE = 1;
private long size;
private long address;
public SuperArray(long size) {
this.size = size;
address = getUnsafe().allocateMemory(size * BYTE);
}
public void set(long i, byte value) {
getUnsafe().putByte(address + i * BYTE, value);
}
public int get(long idx) {
return getUnsafe().getByte(address + idx * BYTE);
}
public long size() {
return size;
}
}
这是堆外内存(off-heap memory)技术,在 java.nio 包中部分可用。这种方式的内存分配不在堆上,且不受 GC 管理,所以必须小心 Unsafe.freeMemory() 的使用。它也不执行任何边界检查,所以任何非法访问可能会导致 JVM 崩溃。这可用于数学计算,代码可操作大数组的数据。此外,这可引起实时程序员的兴趣,可打破 GC 在大数组上延迟的限制。
使用场景十:无锁高并发。这一类的比如:AtomicInteger、AtomicLong 等 java.util.concurrent.atomic 包下的类。当然我们还可以扩展自己的实现。
以上 10 种场景,平常你使用的多少框架中可能都有使用,你只是不知道罢了!
: » 手把手教你用代码实现 Unsafe 的 9 大使用场景
原创文章,作者:bd101bd101,如若转载,请注明出处:https://blog.ytso.com/252069.html