sun.misc.Unsafe类的用法

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/

祝您学习愉快!