快速入門JVM,只看這一篇就夠了(部分)

快速入門JVM,只看這一篇就夠了

1.JVM的整體結構

在這裏插入圖片描述
在運行時數據區中,方法區和堆區是線程共享的,而其他區域是線程獨佔的,這一點要注意。接下來,會有堆JVM的各個結構做更加深入的講解。

2.回顧一下Java代碼的執行流程

從宏觀上看,Java源程序會被編譯成字節碼文件,然後字節碼文件會在不同操作系統上的JVM上被執行,從而得到我們想要的結果。
從微觀上看,會有很多複雜的過程,這篇博客寫得非常清楚,推薦給大家:https://blog.csdn.net/sinat_33087001/article/details/76977437?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

3.JVM的生命週期

(1)虛擬機的啓動

Java虛擬機的啓動時通過引導類加載器(Bootstrap Class Loader)創建一個初始類(Initial Class)來完成的,這個類是由虛擬機的具體實現指定的。

(2)虛擬機的執行

一個運行中的Java虛擬機有一個清晰的任務:執行Java程序
程序開始執行時才運行,程序結束時就停止
執行一個所謂的Java程序時,真正執行的是一個叫做JVM的進程

(3)虛擬機的退出

有如下幾種情況:

  • 程序正常執行結束
  • 程序在執行過程中遇到了異常或錯誤而終止
  • 由於操作系統出現錯誤而導致JVM進程終止
  • 某線程調用Runtime類或System類的exit方法,或Runtime類的halt方法,並且Java安全管理器也允許這次exit或halt操作
  • JNI規範描述了用JNI Invocation API來加載或卸載JVM時
4.類加載器子系統

在這裏插入圖片描述

  • 類加載器子系統負責從文件系統或網絡中加載Class文件,Class文件在文件開頭有特定的文件標識
  • ClassLoader只負責class文件的加載,至於它是否可以運行,則有Execution Engine決定
  • 加載的類信息存放於一塊稱爲方法區的內存空間,除了類的信息外,方法區中還會存放運行時常量池信息,可能還包括字符串字面量和數字常量(這部分常量信息是class文件中常量池部分的內存映射)

注意到:虛擬機自帶的加載器:

  • 引導類加載器(bootstrap),底層是C++,出廠就有的,即從1.0版本開始就有,像Object、String類就由此加載器加載
  • 擴展類加載器(Extension),由Java擴展,爲滿足日益增長的需要
  • 應用程序類加載器(AppClassLoader),Java,也叫做系統類加載器,加載當前應用的classpath的所有類

此外,還有用戶自定義加載器,爲Java.lang.ClassLoader的子類,用戶可以自定義類的加載方式。
可以用下面的圖來表示幾個類加載器之間的關係
在這裏插入圖片描述
爲什麼會有向上指的指針呢?這就要牽扯到一個重要的機制:雙親委派機制。這是啥意思?用一個案例來解釋。假設我在src目錄下建立了一個java.lang包,然後在該包了定義了一個類String,然後編寫main方法,裏面輸出“hello world”,語法上沒有任何錯誤,但程序就是啓動不來。這是爲啥?原因是雙親委派機制保證了Java體系的安全性,即類加載器要加載這個自定義的String類,要先從Bootstrap ClassLoder開始加載起,如果Bootstrap ClassLoader找到了java.lang.String,就加載這個類,很顯然這個類是Java的rt.jar包中的,立馬就找到了;因此也就輪不到System ClassLoader來加載我自定義的這個String類了。
下面給出總結:
當一個類收到了類加載請求,它首先不會嘗試自己加載這個類,而是把這個請求委派給父類完成,每一個層次類加載器都是如此,因此所有的加載請求都是傳送到Bootstrap類加載器中,只有當父類加載器反饋自己無法完成這個請求時(即在它的加載路徑下沒有找到所需要加載的Class),子類加載器纔會嘗試去加載。採用雙親委派的一個好處就是,如加載rt.jar包中的類java.lang.Object時,不管是哪個加載器加載這個類,最終都是委託給頂層的Bootstrap ClassLoader進行加載,這樣就保證了使用不同的類加載器最終得到的都是同一個Object對象。

5.Native,本地方法區/本地方法接口

普通的類當中不能有隻聲明爲實現的方法,但是可以有用native修飾的只聲明未實現的方法。
無法通過編譯,報錯:
在這裏插入圖片描述
用native修飾可以完成:
在這裏插入圖片描述
聲明瞭native的方法就是調用底層操作系統或者C/C++的函數了,與Java沒有任何關係了。
Native Interface本地接口:
本地接口的作用是融合不同的編程語言爲Java所有,Java調用C/C++程序,於是就在內存中專門開闢了一塊區域處理標記爲native的代碼,它的具體做法是Native Method Stack中登記native方法,在Execution Engine執行時加載Native Libraies.
Native Method Stack
具體做法就是Native Method Stack中等級native方法,在Execution Engine執行時加載本地方法庫。

6.PC寄存器,也即程序計數器

每個線程都有一個程序計數器,是線程私有的,就是一個指針,指向方法區中的方法字節碼(用來存儲指向下一條指令的地址,也即將要執行的指令代碼),由執行引擎讀取下一條指令,是一個非常小的內存空間,幾乎可以忽略不記,
這塊內存區域很小,它是當前線程所執行的字節碼的行號指示器,字節碼解釋器通過改變這個計數器的值來選取下一條需要執行的字節碼指令。如果執行的是一個Native方法,那這個計數器是空的。用以完成分支,循環,跳轉,異常處理,線程恢復等基礎功能,不會發送內存溢出(Out Of Memory)錯誤。

7.方法區(Method Area,線程共享,存在垃圾回收)

各線程共享的運行時內存區域,它存儲了每一個類的結構信息,例如運行時的常量池、字段和方法數據、構造函數和普通方法的字節碼內容。不同虛擬機的實現是不一樣的,最典型的就是永生代(PermGen Space)和元空間(Metaspace)。
但是,實例變量存在堆內存中,與方法區無關!

8.棧區(Stack Area)

總的一句話,棧管運行,堆管存儲。
棧內存,主管Java程序的運行,是在線程創建時創建,它的生命週期是跟隨線程的生命週期的,線程結束時棧內存就釋放,不存在垃圾回收的問題,且爲線程私有的。
8種基本數據類型的變量+對象的引用變量+實例方法都是在函數的棧內存中分配的。
棧幀中主要保存3類數據:

  • 本地變量:輸入參數和輸出參數以及方法內的變量
  • 棧操作:記錄出棧、入棧的操作
  • 棧幀數據:包括類文件、方法等

當Java中的方法,被JVM處理時,其就成爲了棧幀
在這裏插入圖片描述
以下圖片更加方便理解:
在這裏插入圖片描述
如果遞歸調用沒有遞歸的結束條件,就會拋出Stack Overflow Error錯誤。
重要:棧+堆+方法區的交互關係:
在這裏插入圖片描述
在HotSpot虛擬機中,是使用指針的方式來訪問對象:Java堆中會存放訪問類元數據的地址,reference存儲的就直接是對象的地址。
比如Student std=new Student(),std存放在棧區,Student對象存放在堆區,用來造Student對象的模板在方法區,三者之間在HotSpot虛擬機中通過指針來引用。

9.堆區(Heap)

在JDK7之前,堆內存邏輯上分爲:新生區+養老區+永久區
JDK8以後,堆內存邏輯上分爲:新生區+養老區+元空間

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章