聊聊序列化(二)使用sun.misc.Unsafe绕过new机制来创建Java对象

在序列化的问题域里面有一个常见的问题,就是反序列化时用何种方式来创建Java对象,因为反序列化的目的是把一段二进制流转化成一个对象。

在Java里面创建对象有几种方式:

1. 显式地调用new语句, 比如 DemoClass demo = new DemoClass()

2. 利用反射机制,通过Class对象的newInstance()方法,比如DemoClass demo = DemoClass.class.newInstance()。 但是有个前提就是必须提供无参的构造函数

3. 利用反射机制,利用Constructor对象来创建对象

这三种方式本质上都是一样的,都是常规的Java创建对象的new机制,不管是显式地还是隐式的。一个new操作,编译成指令后是3条

第一条指令的意思是根据类型分配一块内存区域

第二条指令是把第一条指令返回的内存地址压入操作数栈顶

第三条指令是调用类的构造函数

new机制有个问题就是:. 当类只提供有参的构造函数时,必须使用这个有参的构造函数。

那么问题来了,当反序列化的时候,不可能使用显示地new操作,因为肯定地根据传过来的类型动态地调用。利用newInstance肯定没戏了,因为不能确定这个类是否提供了无参构造函数。只能第三种,利用反射机制,使用Constructor对象来创建对象。

但是Consturctor对象有个约束,就是需要提供参数的类型列表,然后使用Constructor.newInstance方法需要传递相应个数的参数。

在反序列化这个场景下,可以这么做:先根据反射获得Constructor的参数类型列表,然后根据每种类型,构造一个对应的默认值的列表,然后调用Constructor.newInstance()方法。这样可以创建出一个具有默认值的对象。

但是问题又来了,万一这个类的构造函数做了一些特别的操作,比如判断传入的参数的值,如果参数值不符合规范就抛异常,那么创建对象就失败了

public static void testConstructor(){
		try {
			Class[] cls = new Class[] { int.class, int.class };
	        Constructor c = DemoClass.class.getDeclaredConstructor(cls);
			DemoClass obj = (DemoClass) c.newInstance(0, 0);
			System.out.println(obj.getValue1());
			System.out.println(obj.getValue2());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void testConstructorWityParameterTypes(){
		try {
			Constructor[] c = DemoClass.class.getDeclaredConstructors();
			Type[] parameterTypes = c[0].getGenericParameterTypes();
			// 判断type类型,依次设置默认值
			DemoClass obj = (DemoClass) c[0].newInstance(0, 0);
			System.out.println(obj.getValue1());
			System.out.println(obj.getValue2());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

所以有些序列化协议要求被序列化对象必须提供无参的构造函数,这样反序列化的时候可以调用无参的构造函数。

这里提出一种使用sun.misc.Unsafe方法解决这个由于有参构造函数引起的创建Java对象的问题。Unsafe有一个allocateInstance(Class)方法,这个方法只需要传入一个类型就可以创建Java对象了,不正好完美的解决了我们的问题吗?

new操作被解析成了3个步骤,而Unsafe.allocateInstance()方法值做了第一步和第二步,即分配内存空间,返回内存地址,没有做第三步调用构造函数。所以Unsafe.allocateInstance()方法创建的对象都是只有初始值,没有默认值也没有构造函数设置的值,因为它完全没有使用new机制,直接操作内存创建了对象。下面看一个完整的例子,包括如何获得Unsafe对象。

在Eclipse里面引用sun.misc.Unsafe类需要设置一下 Preference --> Java  --> Compiler  -->  Errors/Warnings -->  Forbidden reference ,从Error改成Warning

package com.zc.lock;

import java.lang.reflect.Field;
import sun.misc.Unsafe;

public class UnsafeUtility {

	private static Unsafe unsafe;
	static {
		try {
			Field f = Unsafe.class.getDeclaredField("theUnsafe");
			f.setAccessible(true);
			unsafe = (Unsafe) f.get(null);
		} catch (Exception e) {
		}
	}

	public static Unsafe getUnsafe(){
		return unsafe;
	}

}

一个测试用例

package com.zc.lock.test;

public class DemoClass {
	private int value1;

	private int value2 = 10;

	public DemoClass(int value1, int value2){
		this.value1 = value1;
		this.value2 = value2;
	}

	public int getValue1() {
		return value1;
	}

	public void setValue1(int value1) {
		this.value1 = value1;
	}

	public int getValue2() {
		return value2;
	}

	public void setValue2(int value2) {
		this.value2 = value2;
	}
}

package com.zc.lock.test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Type;

import sun.misc.Unsafe;

import com.zc.lock.UnsafeUtility;

public class Main {
    public static void main(String[] args){
//        testNewObject();
//        testNewInstance();
//        testConstructor();
//        testConstructorWityParameterTypes();
//        testUnsafeAllocateInstance();
    }
    

    public static void testUnsafeAllocateInstance(){
        Unsafe unsafe = UnsafeUtility.getUnsafe();
        
        try {
            DemoClass obj = (DemoClass)unsafe.allocateInstance(DemoClass.class);
            System.out.println(obj.getValue1());
            System.out.println(obj.getValue2());
            obj.setValue1(1);
            obj.setValue2(2);
            System.out.println(obj.getValue1());
            System.out.println(obj.getValue2());
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
    
    public static void testNewObject(){
        DemoClass obj = new DemoClass(1,2);
        System.out.println(obj.getValue1());
        System.out.println(obj.getValue2());
    }
    
    public static void testNewInstance(){
        try {
            DemoClass obj = DemoClass.class.newInstance();
            System.out.println(obj.getValue1());
            System.out.println(obj.getValue2());
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    
    public static void testConstructor(){
        try {
            Class[] cls = new Class[] { int.class, int.class };
            Constructor c = DemoClass.class.getDeclaredConstructor(cls);
            DemoClass obj = (DemoClass) c.newInstance(0, 0);
            System.out.println(obj.getValue1());
            System.out.println(obj.getValue2());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void testConstructorWityParameterTypes(){
        try {
            Constructor[] c = DemoClass.class.getDeclaredConstructors();
            Type[] parameterTypes = c[0].getGenericParameterTypes();
            // 判断type类型,依次设置默认值
            DemoClass obj = (DemoClass) c[0].newInstance(0, 0);
            System.out.println(obj.getValue1());
            System.out.println(obj.getValue2());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
}

 
时间: 2024-11-01 01:40:50

聊聊序列化(二)使用sun.misc.Unsafe绕过new机制来创建Java对象的相关文章

java对象的内存布局(二):利用sun.misc.Unsafe获取类字段的偏移地址和读取字段的值

在上一篇文章中.我们列出了计算java对象大小的几个结论以及jol工具的使用,jol工具的源代码有兴趣的能够去看下.如今我们利用JDK中的sun.misc.Unsafe来计算下字段的偏移地址,一则验证下之前文章中的结论,再则跟jol输出结果对照下.怎样获取sun.misc.Unsafe对象.能够參考这篇文章. public class VO { public int a = 0; public long b = 0; public static String c= "123"; pub

Java Magic. Part 4: sun.misc.Unsafe

原文地址 译文地址 译者:许巧辉 校对:梁海舰 Java是一门安全的编程语言,防止程序员犯很多愚蠢的错误,它们大部分是基于内存管理的.但是,有一种方式可以有意的执行一些不安全.容易犯错的操作,那就是使用Unsafe类. 本文是sun.misc.Unsafe公共API的简要概述,及其一些有趣的用法. Unsafe 实例 在使用Unsafe之前,我们需要创建Unsafe对象的实例.这并不像Unsafe unsafe = new Unsafe()这么简单,因为Unsafe的构造器是私有的.它也有一个静

认识 sun.misc.Unsafe

笼罩在迷雾之中的 Unsafe 私有API,有人认为应该废弃,也有人认为应该开放. [2015年07月28日] Oracle 宣称要在 Java 9 中去除私有 API: sun.misc.Unsafe, 这就像点燃了炸药桶, 遭到 许多开发者的抗议, 他们认为 这会严重破坏Java的生态系统 开源博主 Rafael Winterhalter 在博文 "Understanding sun.misc.Unsafe" 中说, 底层编程(low-level programming) 中经常会

sun.misc.unsafe

Java中大部分错误都是基于内存管理方面的.如果想破坏,可以使用Unsafe这个类. 实例化Unsafe: 下面两种方式是不行的 private Unsafe() {} //私有构造方法 @CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if(!VM.isSystemDomainLoader(var0.getClassLoader())) {//如果不是JDK

一文了解sun.misc.Unsafe

Java语言和JVM平台已经度过了20岁的生日.它最初起源于机顶盒.移动设备和Java-Card,同时也应用在了各种服务器系统中,Java已成为物联网(Internet of Things)的通用语言.我们显然可以看到Java已经无处不在! 但是不那么为人所知的是,Java也广泛应用于各种低延迟的应用中,如游戏服务器和高频率的交易应用.这只所以能够实现要归功于Java的类和包在可见性规则中有一个恰到好处的漏洞,让我们能够使用一个很便利的类,这个类就是sun.misc.Unsafe.这个类从过去到

Java中的sun.misc.Unsafe包

chronicle项目:https://github.com/peter-lawrey/Java-Chronicle 这个项目是利用mmap机制来实现高效的读写数据,号称每秒写入5到20百万条数据. 作者有个测试,写入1百万条log用时0.234秒,用java自带的logger,用时7.347秒. 在看chronicle的源代码,发现一个牛B的利用Unsafe来直接读写内存,从而提高效率的例子. 详细见这个类:https://github.com/peter-lawrey/Java-Chroni

Java sun.misc.unsafe类的使用

Java是一个安全的开发工具,它阻止开发人员犯很多低级的错误,而大部份的错误都是基于内存管理方面的.如果你想搞破坏,可以使用Unsafe这个类.这个类是属于sun.*API中的类,并且它不是J2SE中真正的一部份,因此你可能找不到任何的官方文档,更可悲的是,它也没有比较好的代码文档. 1.实例化sun.misc.Unsafe 如果你尝试创建Unsafe类的实例,基于以下两种原因是不被允许的. 1).Unsafe类的构造函数是私有的: 2).虽然它有静态的getUnsafe()方法,但是如果你尝试

Understanding sun.misc.Unsafe

转自: https://dzone.com/articles/understanding-sunmiscunsafe The biggest competitor to the Java virtual machine might be Microsoft's CLR that hosts languages such as C#. The CLR allows to write unsafe code as an entry gate for low level programming, so

sun.misc.unsafe类的使用

这个帖子是关于JAVA中鲜为人知的特性的后续更新,如果想得到下次在线讨论的更新,请通过邮件订阅,并且不要忘了在评论区留下你的意见和建议. Java是一个安全的开发工具,它阻止开发人员犯很多低级的错误,而大部份的错误都是基于内存管理方面的.如果你想搞破坏,可以使用Unsafe这个类.这个类是属于sun.* API中的类,并且它不是J2SE中真正的一部份,因此你可能找不到任何的官方文档,更可悲的是,它也没有比较好的代码文档. 实例化sun.misc.Unsafe 如果你尝试创建Unsafe类的实例,