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
這是模擬,真實場景就可以將列印改為代理時擴展方法,如資料庫操作時候,開啟關閉事務