1. 程式人生 > >聊聊序列化(二)使用sun.misc.Unsafe繞過new機制來建立Java物件

聊聊序列化(二)使用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();
        }
    }
    
}

 


相關推薦

聊聊序列使用sun.misc.Unsafe繞過new機制建立Java物件

在序列化的問題域裡面有一個常見的問題,就是反序列化時用何種方式來建立Java物件,因為反序列化的目的是把一段二進位制流轉化成一個物件。 在Java裡面建立物件有幾種方式: 1. 顯式地呼叫new語句, 比如 DemoClass demo = new DemoClass()

Kafka 消息的序列與反序列

data string next() com pid tor final AR exce 自定義反序列化類: 對於自定義的avro schema結構,需要有自定義的類在consumer時反序列化,反序列化類實例在consumer構造的時候通過參數傳入 public cl

JSON 序列與反序列使用TypeReference 構建型別安全的異構容器

原文連結:https://www.cnblogs.com/yuyutianxia/p/6051682.html 1. 泛型通常用於集合,如Set和Map等。這樣的用法也就限制了每個容器只能有固定數目的型別引數,一般來說,這也確實是我們想要的。 然而有的時候我們需要更多的靈活性,如資料庫可以用

DispatcherServlet的初始

err root roo dha 其他 還需要 not led end DispatcherServlet的初始化在springmvc的啟動中有講過,這一篇在上一篇的基礎上接著講。DispatcherServlet作為springmvc的前端控制器,還需要初始化其他的模塊。

xml序列和反序列

哈哈 正則表達式 eof AD regex lan value sys 註意 最近項目中需要調用第三方webservice,入參和出參采用xml格式,大致如下: 入參: <?xml version="1.0" encoding="utf-8"?> <

SQL Server 2017 AlwaysOn AG 自動初始

layout nag 服務 ocs 建議 engine idt 防火墻 vpd 必備條件文件路徑要求在 SQL Server 2016 中,自動種子設定要求數據和日誌文件路徑在參與可用性組的每個 SQL Server 實例上均相同。 在 SQL Server 2017 中,

json序列與反序列基礎

創建 反序 key open 通用 json 方式 () text import json # json是所有語言裏通用的 info={‘key‘:‘mode‘,‘lis‘:‘koud‘,‘olo‘:234,} json序列化(只支持簡單的數據類型) f=open(&quo

pickle序列與反序列基礎加優化

ads print follow load fun code 序列化與反序列化 函數 tex import pickle def sh(name):print("hello,",name)#序列化存儲info={‘key‘:‘mode‘,‘lis‘:‘k

數據可視以點畫線

font lis ram cmap title src col 範圍 數據可視化 import matplotlib.pyplot as pltx_values = list(range(1,1000))y_values = [x**2 for x in x_values]

C# 序列Serialize與反序列Deserialize

序列化又稱序列化,是.NET執行時環境用來支援使用者定義型別的流化的機制。其目的是以某種儲存形成使自定義物件持久化,或者將這種物件從一個地方傳輸到另一個地方。 .NET框架提供了兩種種序列化的方式:1、是使用BinaryFormatter進行序列化;2、使用XmlSerializer進行序列

Caffe視覺:權重及輸出視覺用Deep Visualization Toolbox實現

Caffe視覺化(二):權重及輸出視覺化(用Deep Visualization Toolbox實現) 本文記錄了博主在研究Caffe權重及輸出視覺化過程中發現的工具包,包括工具包的安裝、使用和調整(以適應自定義網路)的相關內容。更新於2018.10.26。 文章目錄

java8之行為引數

      上一章講到我們可以使用策略模式使得變動的程式碼塊更具有擴充套件性,實現引數化。可是,如果變動的程式碼塊只使用一次呢,也就是說,我們僅用一次介面的實現類呢?這時,我們會發現,為介面編寫實現類簡直就是浪費。顯然,匿名類此時就派上用場了。 // [App

深入淺出聊聊Kubernetes儲存:搞定持久化儲存

回  顧 在本系列文章的上一篇中,我們講到了PV,PVC,Storage Class以及Provisioner 簡單回顧一下:

python --資料視覺

一、NumPy 1、簡介:  官網連結:http://www.numpy.org/  NumPy是Python語言的一個擴充程式庫。支援高階大量的維度陣列與矩陣運算,此外也針對陣列運算提供大量的數學函式庫 2、基本功能: 快速高效的多維陣列物件ndarray 用於對陣列執行元素級計算以及直

【spring原始碼分析】IOC容器初始

前言:在【spring原始碼分析】IOC容器初始化(一)中已經分析了匯入bean階段,本篇接著分析bean解析階段。 1.解析bean程式呼叫鏈 同樣,先給出解析bean的程式呼叫鏈: 根據程式呼叫鏈,整理出在解析bean過程中主要涉及的類和相關方法。 2.解析bean原始碼分

HADOOP IO詳解——序列1

什麼是IO? I:input 輸入 通常做讀取操作(將不同資料來源的資料讀入到記憶體中,也叫讀取流) O:output 輸出 通常做寫入操作(將記憶體中的資料寫入到不同的資料來源,也叫寫入流)(出記憶體到別的地方) 序列化的作用是什麼?1 資料通訊 2 持久化儲存 為什

HADOOP IO詳解——序列2舉列

package com.hadoop.tv; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import org.apache.hadoop.io.Writ

聊聊高併發結合例項說說執行緒封閉和背後的設計思想

高併發問題拋去架構層面的問題,落實到程式碼層面就是多執行緒的問題。多執行緒的問題主要是執行緒安全的問題(其他還有活躍性問題,效能問題等)。 那什麼是執行緒安全?下面這個定義來自《Java併發程式設計實戰》,這本書強烈推薦,是幾個Java語言的作者合寫的,都是併發程式設計方面

關於資料序列4自定義序列的實現,支援常用集合框架

下面的示例很好的揭示瞭如何實現自定義序列化的方法。 支援byte, byte[], boolean, boolean[], int, int[], long, long[] ,double ,double[], String, String[], 以及Enum, List,Map兩種包

關於資料序列2二進位制流示例

將一個物件保寫進2進位制流,儲存在檔案中,然後從檔案中恢復物件 問題:  像這樣大家覺的直接writeInt(),writeByt();用來跟客戶端通訊和做持久化存在硬碟有什麼問題嗎 protobuf哪裡能看出來是省資源了,他的原理不也是格式化儲存嗎 難