Java jvm 類載入 反射

吳楠予發表於2020-10-24

Java 底層

jvm,類載入,反射

Java語言是跨平臺語言,一段java程式碼,經過編譯成class檔案後,能夠在不同系統的伺服器上執行;因為java語言中有虛擬機器jvm,才有了跨平臺,java為了實現跨平臺,在jvm上投入了很大的研發開發資源。jvm是java的底層,本文學習探討下java的jvm及關聯的類載入和反射知識

JVM

JVM是Java Virtual Machine(Java虛擬機器)的縮寫,JVM是一種用於計算裝置的規範,它是一個虛構出來的計算機,是通過在實際的計算機上模擬模擬各種計算機功能來實現的。

Java語言的一個非常重要的特點就是與平臺的無關性。而使用Java虛擬機器是實現這一特點的關鍵。一般的高階語言如果要在不同的平臺上執行,至少需要編譯成不同的目的碼。而引入Java語言虛擬機器後,Java語言在不同平臺上執行時不需要重新編譯。Java語言使用模式Java虛擬機器遮蔽了與具體平臺相關的資訊,使得Java語言編譯程式只需生成在Java虛擬機器上執行的目的碼(位元組碼),就可以在多種平臺上不加修改地執行。Java虛擬機器在執行位元組碼時,把位元組碼解釋成具體平臺上的機器指令執行。 [1]

jvm的構成

jvm週期:是在java程式執行時執行,程式結束時停止

jvm的基本結構有:類載入子系統、本地方法棧、Java棧、方法區、Java堆、pc暫存器,垃圾回收,執行引擎

類載入子系統

java是面嚮物件語言,邏輯程式碼中的類檔案執行邏輯前,是需要jvm讀取class檔案並校驗初始化後才能使用的,包括變數,方法,構造。

類載入系統可以認為是在使用到java對像時(抽象),對java物件位元組碼的讀取載入預編譯(具體),之後不再載入(讀取校驗一次)。

Java棧

棧是先進後出的結構,java棧時一塊執行緒私有的記憶體空間,可以理解為一個java執行緒對應一個java棧,棧和執行緒密切關聯,棧包含執行緒執行的實時資訊,如當前執行方法地址,方法中的瞬時變數等資訊

方法區

在一個jvm例項的內部,型別資訊被儲存在一個稱為方法區的記憶體邏輯區中。型別資訊是由類載入器在類載入時從類檔案中提取出來的。類(靜態)變數也儲存在方法區中。

Java堆

java堆是和應用程式關係最為密切的記憶體空間,幾乎所有的物件都存放在堆上。並且java堆是完全自動化管理的,通過垃圾回收機制,垃圾物件會被自動清理,而不需要顯示的釋放。

pc暫存器

存放計算機下一步要執行的指令的地址,

垃圾回收

因為程式執行沒建立一個物件都需要使用硬體的記憶體資源,不能無限使用,jvm的垃圾回收能夠自動回收不再使用的java物件,使記憶體空間有效利用。垃圾回收執行緒是後臺執行的,不需要認為回收記憶體垃圾,即使有垃圾回收方法呼叫,但並不能控制jvm如何去將一個物件失效回收。

執行引擎

Java 位元組碼指令指向特定邏輯得本地機器碼,而JVM 解釋執行Java位元組碼指令時,會直接呼叫位元組碼指向得本地機器碼;

java底層由C語言編寫,執行java程式時,jvm每讀取一個位元組碼指令動作,執行引擎就解析解釋執行本地系統對應的本地機器碼。

類載入

虛擬機器把描述類的資料從 Class 檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的 Java 型別,這就是虛擬機器的類載入機制。

在Java語言裡面,型別的載入、連線和初始化過程都是在程式執行期間完成的

雙親委派機制

作為軟體開發語言,java在安全方面也有很高的要求,所以類載入是有一套規則的,jre是java執行時的環境,包括很多基本類,如java.lang.String 是字串類,這個類很基礎也很重要,那在載入的時候不能允許載入的String類被篡改,java保證類載入安全,首先看是否已經載入,如果沒有檢視核心庫是否有此類,沒有此類才會去擴充套件環境找類檔案載入,這種機制保證了類在載入時的唯一性和安全性。

java類載入一般來說是詢問自己的parentClassLoader 載入,如果沒有載入成功,才自己載入,載入順序是自上而下

java.lang.ClassLoader 載入類方法

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name); //0.是否已載入
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false); //1.沒有載入,首先通過父類載入器載入
                    } else {
                        c = findBootstrapClassOrNull(name); //1.沒有父類載入器時載入方式
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) { 
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);
					//如果父類沒有載入到類,則使用findClass方法去載入類(這個方法可以重寫自定義)
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

反射

反射是Java重要的技術點,在框架開發,AOP切面程式設計代理等方面都需要反射方面的技術去實現。

Java反射機制主要提供了以下功能:

  • 在執行時判斷任意一個物件所屬的類。
  • 在執行時構造任意一個類的物件。
  • 在執行時判斷任意一個類所具有的成員變數和方法。
  • 在執行時呼叫任意一個物件的方法。
  • 生成動態代理。

反射相關的類

Class 類的位元組碼物件

Field 類的屬性

Method 類的方法

Constructor 類的構造方法

Annotation 類(方法欄位)的註解

反射的使用

一般使用

模擬事務的註解

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface defTransaction {

}

普通封裝物件

public interface People {

	String getName();
	void setName(String name);
	Integer getAge();
	void setAge(Integer age);
	BigDecimal getMoney();
	void setMoney(BigDecimal money);
	@defTransaction
	void addMoney(BigDecimal addNum);
	@defTransaction
	void subTractMoney(BigDecimal subNum);
	
}

public class TestPeople implements People{
	
	// 姓名
	public String name;
	
	// 年齡
	private Integer age;
	
	// 錢
	private BigDecimal money;
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public BigDecimal getMoney() {
		return money;
	}
	public void setMoney(BigDecimal money) {
		this.money = money;
	}

	@Override
	public void addMoney(BigDecimal addNum) {
		this.money = this.money.add(addNum);
		
	}

	@Override
	public void subTractMoney(BigDecimal subNum) {
		this.money = this.money.subtract(subNum);
	}		
}

反射測試類

public class ReflectTest {

	public static void main(String[] args) {
		// 普通物件建立 使用new
		People testPeople = new TestPeople();
		testPeople.setName("Frank");
		testPeople.setAge(18);
		testPeople.setMoney(new BigDecimal(10));
		System.out.println("json:" + JsonUtil.objectToJson(testPeople));
		
		// 反射建立物件 class.newInstance()
		ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
		try {
			Class<?> clazz = contextClassLoader.loadClass("com.domoment.leaves.common.util.reflect.TestPeople");
			if(clazz != null) {
					Object people = clazz.newInstance();
					System.out.println("newInstance start json:" + JsonUtil.objectToJson(people));
					
					// 通過反射執行方法
					Method setName = clazz.getMethod("setName", String.class);
					setName.invoke(people, "inoverFrank");
					
					
					System.out.println("newInstance end json:" + JsonUtil.objectToJson(people));
					
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

使用反射實現代理

代理類DefProxy (People是被代理類)


public class DefProxy implements InvocationHandler{

	 // 這個就是我們要代理的真實物件
    private Object subject;
    
    //    構造方法,給我們要代理的真實物件賦初值
    public DefProxy(Object subject){
        this.subject = subject;
    }
    
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Annotation[] annotations = method.getDeclaredAnnotations();
		boolean transactionOpen = false;
		for (Annotation annotation : annotations) {
			if(annotation instanceof defTransaction) {
				transactionOpen = true;
				break;
			}
		}
		if(transactionOpen) { //當方法上有 defTransaction 註解時,執行方法前開啟事務
			System.out.println("open Transaction");
		}
		System.out.println("proxy:" + method.getName());
		Object result = method.invoke(subject, args);
		
		if(transactionOpen) { //當方法上有 defTransaction 註解時,執行方法後關閉事務
			System.out.println("close Transaction");
		}
		return result;
	}

}

代理測試程式碼

public class ReflectTest {

	public static void main(String[] args) {
		// 反射建立物件
		ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
		try {
			Class<?> clazz = contextClassLoader.loadClass("com.domoment.leaves.common.util.reflect.TestPeople");
			if(clazz != null) {
					Object people = clazz.newInstance();
					System.out.println("newInstance start json:" + JsonUtil.objectToJson(people));
					
					
					Method setName = clazz.getMethod("setName", String.class);
					setName.invoke(people, "inoverFrank");
					
					System.out.println("newInstance end json:" + JsonUtil.objectToJson(people));
					
					InvocationHandler handler = new DefProxy(people);
					
                	// 構造代理物件
					People proxyPeople = (People)Proxy.
							newProxyInstance(handler.getClass().getClassLoader(), people.getClass().getInterfaces(), handler);

					proxyPeople.setName("proxySetFrank");
					proxyPeople.setAge(20);
					proxyPeople.setMoney(new BigDecimal(999));
					System.out.println("proxyPeople end json:" + JsonUtil.objectToJson(people));
					proxyPeople.addMoney(new BigDecimal(20));
					System.out.println("proxyPeople add json:" + JsonUtil.objectToJson(people));
					proxyPeople.subTractMoney(new BigDecimal(17));
					System.out.println("proxyPeople end json:" + JsonUtil.objectToJson(people));
					
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

控制檯列印

newInstance start json:{}
newInstance end json:{"name":"inoverFrank"}
proxy:setName
proxy:setAge
proxy:setMoney
proxyPeople end json:{"name":"proxySetFrank","age":20,"money":999}
open Transaction
proxy:addMoney
close Transaction
proxyPeople add json:{"name":"proxySetFrank","age":20,"money":1019}
open Transaction
proxy:subTractMoney
close Transaction
proxyPeople end json:{"name":"proxySetFrank","age":20,"money":1002}

可以看到方法上有 defTransaction 註解的時候,

方法執行前 列印 open Transaction

方法執行後 列印 close Transaction

這是模擬,真實場景就可以將列印改為代理時擴充套件方法,如資料庫操作時候,開啟關閉事務

相關文章