sun.misc.Unsafe
类的用法
原文: https://howtodoinjava.com/java/basics/usage-of-class-sun-misc-unsafe/
这篇文章是有关 Java 鲜为人知的特性的讨论顺序的下一个更新。 请通过电子邮件订阅,以在下一次讨论进行时进行更新。 并且不要忘记在评论部分表达您的观点。
Java 是一种安全的编程语言,可以防止程序员犯很多愚蠢的错误,这些错误大多数是基于内存管理的。 但是,如果您决定将其弄乱,则可以使用Unsafe
类。 此类是sun.*
API,其中并不是 J2SE 的真正组成部分,因此您可能找不到任何正式文档。 可悲的是,它也没有任何好的代码文档。
sun.misc.Unsafe
的实例化
如果尝试创建Unsafe
类的实例,则由于两个原因,将不允许您这样做。
1)不安全类具有私有构造器。
2)它也具有静态的getUnsafe()
方法,但是如果您朴素地尝试调用Unsafe.getUnsafe()
,则可能会得到SecurityException
。 此类只能从受信任的代码实例化。
但是总有一些解决方法。 创建实例的类似的简单方法是使用反射:
Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
注意:您的 IDE,例如 eclipse 可能显示与访问限制有关的错误。 不用担心,继续运行程序。 它将运行。
现在到主要部分。 使用此对象,您可以执行“有趣的”任务。
sun.misc.Unsafe
的使用
1)创建不受限制的实例
使用allocateInstance()
方法,您可以创建类的实例,而无需调用其构造器代码,初始化代码,各种 JVM 安全检查以及所有其他低级内容。 即使类具有私有构造器,也可以使用此方法创建新实例。
所有单例爱好者的噩梦。 伙计们,您只是无法轻松应对这种威胁。
举个例子:
public class UnsafeDemo
{
public static void main(String[] args) throws NoSuchFieldException, SecurityException,
IllegalArgumentException, IllegalAccessException, InstantiationException
{
Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
//This creates an instance of player class without any initialization
Player p = (Player) unsafe.allocateInstance(Player.class);
System.out.println(p.getAge()); //Print 0
p.setAge(45); //Let's now set age 45 to un-initialized object
System.out.println(p.getAge()); //Print 45
System.out.println(new Player().getAge()); //This the normal way to get fully initialized object; Prints 50
}
}
class Player{
private int age = 12;
public Player(){ //Even if you create this constructor private;
//You can initialize using Unsafe.allocateInstance()
this.age = 50;
}
public int getAge(){
return this.age;
}
public void setAge(int age){
this.age = age;
}
}
Output:
0
45
50
2)使用直接内存访问的浅克隆
通常如何进行浅克隆? 在clone(){..}
方法中调用super.clone()
吧? 这里的问题是,您必须实现Cloneable
接口,然后在要实现浅层克隆的所有类中覆盖clone()
方法。 懒惰的开发人员要付出太多努力。
我不建议这样做,但是使用不安全的方法,我们可以在几行中创建浅表克隆,最好的部分是它可以与任何类一起使用,就像某些工具方法一样。
诀窍是将对象的字节复制到内存中的另一个位置,然后将该对象类型转换为克隆的对象类型。
3)黑客的密码安全性
这看起来很有趣吗? 是的,是的。 开发人员创建密码或将密码存储在字符串中,然后在应用程序代码中使用它们。 使用密码后,更聪明的开发人员将字符串引用设置为NULL
,以便不再对其进行引用,并且可以轻松地对其进行垃圾回收。
但是从那时起,您对垃圾回收器启动时的引用为null
,该字符串实例位于字符串池中。 对您的系统进行的复杂攻击将能够读取您的内存区域,从而也可以访问密码。 机会很低,但他们在这里。
因此,建议使用char []
存储密码,以便在使用后可以遍历数组并使每个字符变脏/变空。
另一种方法是使用我们的魔术类“不安全”。 在这里,您将创建另一个长度与密码相同的临时字符串,并存储“?
”或为临时密码中的每个字符输入“*
”(或任何字母)。 完成密码逻辑操作后,您只需将临时密码(例如????????
)的字节复制到原始密码上即可。 这意味着用临时密码覆盖原始密码。
示例代码如下所示。
String password = new String("l00k@myHor$e");
String fake = new String(password.replaceAll(".", "?"));
System.out.println(password); // l00k@myHor$e
System.out.println(fake); // ????????????
getUnsafe().copyMemory(fake, 0L, null, toAddress(password), sizeOf(password));
System.out.println(password); // ????????????
System.out.println(fake); // ????????????
在运行时动态创建类
我们可以在运行时创建类,例如从已编译的.class
文件中。 要将读取的类内容执行到字节数组,然后将其传递给defineClass
方法。
//Sample code to craeet classes
byte[] classContents = getClassContent();
Class c = getUnsafe().defineClass(null, classContents, 0, classContents.length);
c.getMethod("a").invoke(c.newInstance(), null);
//Method to read .class file
private static byte[] getClassContent() throws Exception {
File f = new File("/home/mishadoff/tmp/A.class");
FileInputStream input = new FileInputStream(f);
byte[] content = new byte[(int)f.length()];
input.read(content);
input.close();
return content;
}
4)超大数组
如您所知,Integer.MAX_VALUE
常量是 java 数组的最大大小。 如果要构建真正的大数组(尽管在常规应用程序中没有实际需要),则可以为此使用直接内存分配。
以此类的示例为例,该类创建的顺序存储器(数组)的大小是允许的大小的两倍。
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;
}
}
用法示例:
long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;
SuperArray array = new SuperArray(SUPER_SIZE);
System.out.println("Array size:" + array.size()); // 4294967294
for (int i = 0; i < 100; i++) {
array.set((long)Integer.MAX_VALUE + i, (byte)3);
sum += array.get((long)Integer.MAX_VALUE + i);
}
System.out.println("Sum of 100 elements:" + sum); // 300
请注意,这会导致 JVM 崩溃。
总结
sun.misc.Unsafe
提供了几乎无限的能力来探索和修改 VM 的运行时数据结构。 尽管事实上这些特性几乎不能在 Java 开发本身中使用,但是对于希望在不进行 C++代码调试的情况下研究 HotSpot VM 或需要创建临时分析工具的任何人来说,Unsafe
都是一个不错的工具。
参考:
http://mishadoff.github.io/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
祝您学习愉快!