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