'阿里架構師深度解析java技術原理,小白如何學習java到架構師'

"
"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

阿里架構師深度解析java技術原理,小白如何學習java到架構師

對於JVM的學習,在我看來這麼幾個部分最重要:

Java代碼編譯和執行的整個過程

JVM內存管理及垃圾回收機制

下面分別對這幾部分進行說明:

2、Java代碼編譯和執行的整個過程

也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

阿里架構師深度解析java技術原理,小白如何學習java到架構師

對於JVM的學習,在我看來這麼幾個部分最重要:

Java代碼編譯和執行的整個過程

JVM內存管理及垃圾回收機制

下面分別對這幾部分進行說明:

2、Java代碼編譯和執行的整個過程

也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(2)Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

阿里架構師深度解析java技術原理,小白如何學習java到架構師

對於JVM的學習,在我看來這麼幾個部分最重要:

Java代碼編譯和執行的整個過程

JVM內存管理及垃圾回收機制

下面分別對這幾部分進行說明:

2、Java代碼編譯和執行的整個過程

也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(2)Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java代碼編譯和執行的整個過程包含了以下三個重要的機制:

·Java源碼編譯機制

·類加載機制

·類執行機制

(1)Java源碼編譯機制

Java 源碼編譯由以下三個過程組成:

①分析和輸入到符號表

②註解處理

③語義分析和生成class文件

流程圖如下所示:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

阿里架構師深度解析java技術原理,小白如何學習java到架構師

對於JVM的學習,在我看來這麼幾個部分最重要:

Java代碼編譯和執行的整個過程

JVM內存管理及垃圾回收機制

下面分別對這幾部分進行說明:

2、Java代碼編譯和執行的整個過程

也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(2)Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java代碼編譯和執行的整個過程包含了以下三個重要的機制:

·Java源碼編譯機制

·類加載機制

·類執行機制

(1)Java源碼編譯機制

Java 源碼編譯由以下三個過程組成:

①分析和輸入到符號表

②註解處理

③語義分析和生成class文件

流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

最後生成的class文件由以下部分組成:

①結構信息:包括class文件格式版本號及各部分的數量與大小的信息

②元數據:對應於Java源碼中聲明與常量的信息。包含類/繼承的超類/實現的接口的聲明信息、域與方法聲明信息和常量池

③方法信息:對應Java源碼中語句和表達式對應的信息。包含字節碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符號信息

(2)類加載機制

JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關係和加載順序可以由下圖來描述:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

阿里架構師深度解析java技術原理,小白如何學習java到架構師

對於JVM的學習,在我看來這麼幾個部分最重要:

Java代碼編譯和執行的整個過程

JVM內存管理及垃圾回收機制

下面分別對這幾部分進行說明:

2、Java代碼編譯和執行的整個過程

也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(2)Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java代碼編譯和執行的整個過程包含了以下三個重要的機制:

·Java源碼編譯機制

·類加載機制

·類執行機制

(1)Java源碼編譯機制

Java 源碼編譯由以下三個過程組成:

①分析和輸入到符號表

②註解處理

③語義分析和生成class文件

流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

最後生成的class文件由以下部分組成:

①結構信息:包括class文件格式版本號及各部分的數量與大小的信息

②元數據:對應於Java源碼中聲明與常量的信息。包含類/繼承的超類/實現的接口的聲明信息、域與方法聲明信息和常量池

③方法信息:對應Java源碼中語句和表達式對應的信息。包含字節碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符號信息

(2)類加載機制

JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關係和加載順序可以由下圖來描述:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

①Bootstrap ClassLoader

負責加載$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類

②Extension ClassLoader

負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

③App ClassLoader

負責記載classpath中指定的jar包及目錄中class

④Custom ClassLoader

屬於應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader

加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。

(3)類執行機制

JVM是基於堆棧的虛擬機。JVM為每個新創建的線程都分配一個堆棧.也就是說,對於一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。

JVM執行class字節碼,線程創建後,都會產生程序計數器(PC)和棧(Stack),程序計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次調用,而棧幀又是有局部變量區和操作數棧兩部分組成,局部變量區用於存放方法中的局部變量和參數,操作數棧中用於存放方法執行過程中產生的中間結果。棧的結構如下圖所示:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

阿里架構師深度解析java技術原理,小白如何學習java到架構師

對於JVM的學習,在我看來這麼幾個部分最重要:

Java代碼編譯和執行的整個過程

JVM內存管理及垃圾回收機制

下面分別對這幾部分進行說明:

2、Java代碼編譯和執行的整個過程

也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(2)Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java代碼編譯和執行的整個過程包含了以下三個重要的機制:

·Java源碼編譯機制

·類加載機制

·類執行機制

(1)Java源碼編譯機制

Java 源碼編譯由以下三個過程組成:

①分析和輸入到符號表

②註解處理

③語義分析和生成class文件

流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

最後生成的class文件由以下部分組成:

①結構信息:包括class文件格式版本號及各部分的數量與大小的信息

②元數據:對應於Java源碼中聲明與常量的信息。包含類/繼承的超類/實現的接口的聲明信息、域與方法聲明信息和常量池

③方法信息:對應Java源碼中語句和表達式對應的信息。包含字節碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符號信息

(2)類加載機制

JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關係和加載順序可以由下圖來描述:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

①Bootstrap ClassLoader

負責加載$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類

②Extension ClassLoader

負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

③App ClassLoader

負責記載classpath中指定的jar包及目錄中class

④Custom ClassLoader

屬於應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader

加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。

(3)類執行機制

JVM是基於堆棧的虛擬機。JVM為每個新創建的線程都分配一個堆棧.也就是說,對於一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。

JVM執行class字節碼,線程創建後,都會產生程序計數器(PC)和棧(Stack),程序計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次調用,而棧幀又是有局部變量區和操作數棧兩部分組成,局部變量區用於存放方法中的局部變量和參數,操作數棧中用於存放方法執行過程中產生的中間結果。棧的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM內存管理及垃圾回收機制

JVM內存結構分為:方法區(method),棧內存(stack),堆內存(heap),本地方法棧(java中的jni調用),結構圖如下所示:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

阿里架構師深度解析java技術原理,小白如何學習java到架構師

對於JVM的學習,在我看來這麼幾個部分最重要:

Java代碼編譯和執行的整個過程

JVM內存管理及垃圾回收機制

下面分別對這幾部分進行說明:

2、Java代碼編譯和執行的整個過程

也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(2)Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java代碼編譯和執行的整個過程包含了以下三個重要的機制:

·Java源碼編譯機制

·類加載機制

·類執行機制

(1)Java源碼編譯機制

Java 源碼編譯由以下三個過程組成:

①分析和輸入到符號表

②註解處理

③語義分析和生成class文件

流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

最後生成的class文件由以下部分組成:

①結構信息:包括class文件格式版本號及各部分的數量與大小的信息

②元數據:對應於Java源碼中聲明與常量的信息。包含類/繼承的超類/實現的接口的聲明信息、域與方法聲明信息和常量池

③方法信息:對應Java源碼中語句和表達式對應的信息。包含字節碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符號信息

(2)類加載機制

JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關係和加載順序可以由下圖來描述:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

①Bootstrap ClassLoader

負責加載$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類

②Extension ClassLoader

負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

③App ClassLoader

負責記載classpath中指定的jar包及目錄中class

④Custom ClassLoader

屬於應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader

加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。

(3)類執行機制

JVM是基於堆棧的虛擬機。JVM為每個新創建的線程都分配一個堆棧.也就是說,對於一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。

JVM執行class字節碼,線程創建後,都會產生程序計數器(PC)和棧(Stack),程序計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次調用,而棧幀又是有局部變量區和操作數棧兩部分組成,局部變量區用於存放方法中的局部變量和參數,操作數棧中用於存放方法執行過程中產生的中間結果。棧的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM內存管理及垃圾回收機制

JVM內存結構分為:方法區(method),棧內存(stack),堆內存(heap),本地方法棧(java中的jni調用),結構圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(1)堆內存(heap)

所有通過new創建的對象的內存都在堆中分配,其大小可以通過-Xmx和-Xms來控制。

操作系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣代碼中的delete語句才能正確的釋放本內存空間。但由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閒鏈表中。這時由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便。另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,它不是在堆,也不是在棧,而是直接在進程的地址空間中保留一塊內存,雖然這種方法用起來最不方便,但是速度快,也是最靈活的。堆內存是向高地址擴展的數據結構,是不連續的內存區域。由於系統是用鏈表來存儲的空閒內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大

(2)棧內存(stack)

在Windows下, 棧是向低地址擴展的數據結構,是一塊連續的內存區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是固定的(是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。只要棧的剩餘空間大於所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。 由系統自動分配,速度較快。但程序員是無法控制的。

堆內存與棧內存需要說明:

基礎數據類型直接在棧空間分配,方法的形式參數,直接在棧空間分配,當方法調用完成後從棧空間回收。引用數據類型,需要用new來創建,既在棧空間分配一個地址空間,又在堆空間分配對象的類變量 。方法的引用參數,在棧空間分配一個地址空間,並指向堆空間的對象區,當方法調用完成後從棧空間回收。局部變量new出來時,在棧空間和堆空間中分配空間,當局部變量生命週期結束後,棧空間立刻被回收,堆空間區域等待GC回收。方法調用時傳入的literal參數,先在棧空間分配,在方法調用完成後從棧空間收回。字符串常量、static在DATA區域分配,this在堆空間分配。數組既在棧空間分配數組名稱,又在堆空間分配數組實際的大小。

如:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

阿里架構師深度解析java技術原理,小白如何學習java到架構師

對於JVM的學習,在我看來這麼幾個部分最重要:

Java代碼編譯和執行的整個過程

JVM內存管理及垃圾回收機制

下面分別對這幾部分進行說明:

2、Java代碼編譯和執行的整個過程

也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(2)Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java代碼編譯和執行的整個過程包含了以下三個重要的機制:

·Java源碼編譯機制

·類加載機制

·類執行機制

(1)Java源碼編譯機制

Java 源碼編譯由以下三個過程組成:

①分析和輸入到符號表

②註解處理

③語義分析和生成class文件

流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

最後生成的class文件由以下部分組成:

①結構信息:包括class文件格式版本號及各部分的數量與大小的信息

②元數據:對應於Java源碼中聲明與常量的信息。包含類/繼承的超類/實現的接口的聲明信息、域與方法聲明信息和常量池

③方法信息:對應Java源碼中語句和表達式對應的信息。包含字節碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符號信息

(2)類加載機制

JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關係和加載順序可以由下圖來描述:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

①Bootstrap ClassLoader

負責加載$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類

②Extension ClassLoader

負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

③App ClassLoader

負責記載classpath中指定的jar包及目錄中class

④Custom ClassLoader

屬於應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader

加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。

(3)類執行機制

JVM是基於堆棧的虛擬機。JVM為每個新創建的線程都分配一個堆棧.也就是說,對於一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。

JVM執行class字節碼,線程創建後,都會產生程序計數器(PC)和棧(Stack),程序計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次調用,而棧幀又是有局部變量區和操作數棧兩部分組成,局部變量區用於存放方法中的局部變量和參數,操作數棧中用於存放方法執行過程中產生的中間結果。棧的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM內存管理及垃圾回收機制

JVM內存結構分為:方法區(method),棧內存(stack),堆內存(heap),本地方法棧(java中的jni調用),結構圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(1)堆內存(heap)

所有通過new創建的對象的內存都在堆中分配,其大小可以通過-Xmx和-Xms來控制。

操作系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣代碼中的delete語句才能正確的釋放本內存空間。但由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閒鏈表中。這時由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便。另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,它不是在堆,也不是在棧,而是直接在進程的地址空間中保留一塊內存,雖然這種方法用起來最不方便,但是速度快,也是最靈活的。堆內存是向高地址擴展的數據結構,是不連續的內存區域。由於系統是用鏈表來存儲的空閒內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大

(2)棧內存(stack)

在Windows下, 棧是向低地址擴展的數據結構,是一塊連續的內存區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是固定的(是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。只要棧的剩餘空間大於所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。 由系統自動分配,速度較快。但程序員是無法控制的。

堆內存與棧內存需要說明:

基礎數據類型直接在棧空間分配,方法的形式參數,直接在棧空間分配,當方法調用完成後從棧空間回收。引用數據類型,需要用new來創建,既在棧空間分配一個地址空間,又在堆空間分配對象的類變量 。方法的引用參數,在棧空間分配一個地址空間,並指向堆空間的對象區,當方法調用完成後從棧空間回收。局部變量new出來時,在棧空間和堆空間中分配空間,當局部變量生命週期結束後,棧空間立刻被回收,堆空間區域等待GC回收。方法調用時傳入的literal參數,先在棧空間分配,在方法調用完成後從棧空間收回。字符串常量、static在DATA區域分配,this在堆空間分配。數組既在棧空間分配數組名稱,又在堆空間分配數組實際的大小。

如:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(3)本地方法棧(java中的jni調用)

用於支持native方法的執行,存儲了每個native方法調用的狀態。對於本地方法接口,實現JVM並不要求一定要有它的支持,甚至可以完全沒有。Sun公司實現Java本地接口(JNI)是出於可移植性的考慮,當然我們也可以設計出其它的本地接口來代替Sun公司的JNI。但是這些設計與實現是比較複雜的事情,需要確保垃圾回收器不會將那些正在被本地方法調用的對象釋放掉。

(4)方法區(method)

它保存方法代碼(編譯後的java代碼)和符號表。存放了要加載的類信息、靜態變量、final類型的常量、屬性和方法信息。JVM用持久代(Permanet Generation)來存放方法區,可通過-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值。

垃圾回收機制

堆裡聚集了所有由應用程序創建的對象,JVM也有對應的指令比如 new, newarray, anewarray和multianewarray,然並沒有向 C++ 的 delete,free 等釋放空間的指令,Java的所有釋放都由 GC 來做,GC除了做回收內存之外,另外一個重要的工作就是內存的壓縮,這個在其他的語言中也有類似的實現,相比 C++ 不僅好用,而且增加了安全性,當然她也有弊端,比如性能這個大問題。

4、Java虛擬機的運行過程示例

上面對虛擬機的各個部分進行了比較詳細的說明,下面通過一個具體的例子來分析它的運行過程。

虛擬機通過調用某個指定類的方法main啟動,傳遞給main一個字符串數組參數,使指定的類被裝載,同時鏈接該類所使用的其它的類型,並且初始化它們。例如對於程序:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

阿里架構師深度解析java技術原理,小白如何學習java到架構師

對於JVM的學習,在我看來這麼幾個部分最重要:

Java代碼編譯和執行的整個過程

JVM內存管理及垃圾回收機制

下面分別對這幾部分進行說明:

2、Java代碼編譯和執行的整個過程

也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(2)Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java代碼編譯和執行的整個過程包含了以下三個重要的機制:

·Java源碼編譯機制

·類加載機制

·類執行機制

(1)Java源碼編譯機制

Java 源碼編譯由以下三個過程組成:

①分析和輸入到符號表

②註解處理

③語義分析和生成class文件

流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

最後生成的class文件由以下部分組成:

①結構信息:包括class文件格式版本號及各部分的數量與大小的信息

②元數據:對應於Java源碼中聲明與常量的信息。包含類/繼承的超類/實現的接口的聲明信息、域與方法聲明信息和常量池

③方法信息:對應Java源碼中語句和表達式對應的信息。包含字節碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符號信息

(2)類加載機制

JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關係和加載順序可以由下圖來描述:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

①Bootstrap ClassLoader

負責加載$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類

②Extension ClassLoader

負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

③App ClassLoader

負責記載classpath中指定的jar包及目錄中class

④Custom ClassLoader

屬於應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader

加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。

(3)類執行機制

JVM是基於堆棧的虛擬機。JVM為每個新創建的線程都分配一個堆棧.也就是說,對於一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。

JVM執行class字節碼,線程創建後,都會產生程序計數器(PC)和棧(Stack),程序計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次調用,而棧幀又是有局部變量區和操作數棧兩部分組成,局部變量區用於存放方法中的局部變量和參數,操作數棧中用於存放方法執行過程中產生的中間結果。棧的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM內存管理及垃圾回收機制

JVM內存結構分為:方法區(method),棧內存(stack),堆內存(heap),本地方法棧(java中的jni調用),結構圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(1)堆內存(heap)

所有通過new創建的對象的內存都在堆中分配,其大小可以通過-Xmx和-Xms來控制。

操作系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣代碼中的delete語句才能正確的釋放本內存空間。但由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閒鏈表中。這時由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便。另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,它不是在堆,也不是在棧,而是直接在進程的地址空間中保留一塊內存,雖然這種方法用起來最不方便,但是速度快,也是最靈活的。堆內存是向高地址擴展的數據結構,是不連續的內存區域。由於系統是用鏈表來存儲的空閒內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大

(2)棧內存(stack)

在Windows下, 棧是向低地址擴展的數據結構,是一塊連續的內存區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是固定的(是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。只要棧的剩餘空間大於所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。 由系統自動分配,速度較快。但程序員是無法控制的。

堆內存與棧內存需要說明:

基礎數據類型直接在棧空間分配,方法的形式參數,直接在棧空間分配,當方法調用完成後從棧空間回收。引用數據類型,需要用new來創建,既在棧空間分配一個地址空間,又在堆空間分配對象的類變量 。方法的引用參數,在棧空間分配一個地址空間,並指向堆空間的對象區,當方法調用完成後從棧空間回收。局部變量new出來時,在棧空間和堆空間中分配空間,當局部變量生命週期結束後,棧空間立刻被回收,堆空間區域等待GC回收。方法調用時傳入的literal參數,先在棧空間分配,在方法調用完成後從棧空間收回。字符串常量、static在DATA區域分配,this在堆空間分配。數組既在棧空間分配數組名稱,又在堆空間分配數組實際的大小。

如:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(3)本地方法棧(java中的jni調用)

用於支持native方法的執行,存儲了每個native方法調用的狀態。對於本地方法接口,實現JVM並不要求一定要有它的支持,甚至可以完全沒有。Sun公司實現Java本地接口(JNI)是出於可移植性的考慮,當然我們也可以設計出其它的本地接口來代替Sun公司的JNI。但是這些設計與實現是比較複雜的事情,需要確保垃圾回收器不會將那些正在被本地方法調用的對象釋放掉。

(4)方法區(method)

它保存方法代碼(編譯後的java代碼)和符號表。存放了要加載的類信息、靜態變量、final類型的常量、屬性和方法信息。JVM用持久代(Permanet Generation)來存放方法區,可通過-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值。

垃圾回收機制

堆裡聚集了所有由應用程序創建的對象,JVM也有對應的指令比如 new, newarray, anewarray和multianewarray,然並沒有向 C++ 的 delete,free 等釋放空間的指令,Java的所有釋放都由 GC 來做,GC除了做回收內存之外,另外一個重要的工作就是內存的壓縮,這個在其他的語言中也有類似的實現,相比 C++ 不僅好用,而且增加了安全性,當然她也有弊端,比如性能這個大問題。

4、Java虛擬機的運行過程示例

上面對虛擬機的各個部分進行了比較詳細的說明,下面通過一個具體的例子來分析它的運行過程。

虛擬機通過調用某個指定類的方法main啟動,傳遞給main一個字符串數組參數,使指定的類被裝載,同時鏈接該類所使用的其它的類型,並且初始化它們。例如對於程序:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

編譯後在命令行模式下鍵入: java HelloApp run virtual machine

將通過調用HelloApp的方法main來啟動java虛擬機,傳遞給main一個包含三個字符串"run"、"virtual"、"machine"的數組。現在我們略述虛擬機在執行HelloApp時可能採取的步驟。

開始試圖執行類HelloApp的main方法,發現該類並沒有被裝載,也就是說虛擬機當前不包含該類的二進制代表,於是虛擬機使用ClassLoader試圖尋找這樣的二進制代表。如果這個進程失敗,則拋出一個異常。類被裝載後同時在main方法被調用之前,必須對類HelloApp與其它類型進行鏈接然後初始化。鏈接包含三個階段:檢驗,準備和解析。檢驗檢查被裝載的主類的符號和語義,準備則創建類或接口的靜態域以及把這些域初始化為標準的默認值,解析負責檢查主類對其它類或接口的符號引用,在這一步它是可選的。類的初始化是對類中聲明的靜態初始化函數和靜態域的初始化構造方法的執行。一個類在初始化之前它的父類必須被初始化。整個過程如下:

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

阿里架構師深度解析java技術原理,小白如何學習java到架構師

對於JVM的學習,在我看來這麼幾個部分最重要:

Java代碼編譯和執行的整個過程

JVM內存管理及垃圾回收機制

下面分別對這幾部分進行說明:

2、Java代碼編譯和執行的整個過程

也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(2)Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java代碼編譯和執行的整個過程包含了以下三個重要的機制:

·Java源碼編譯機制

·類加載機制

·類執行機制

(1)Java源碼編譯機制

Java 源碼編譯由以下三個過程組成:

①分析和輸入到符號表

②註解處理

③語義分析和生成class文件

流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

最後生成的class文件由以下部分組成:

①結構信息:包括class文件格式版本號及各部分的數量與大小的信息

②元數據:對應於Java源碼中聲明與常量的信息。包含類/繼承的超類/實現的接口的聲明信息、域與方法聲明信息和常量池

③方法信息:對應Java源碼中語句和表達式對應的信息。包含字節碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符號信息

(2)類加載機制

JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關係和加載順序可以由下圖來描述:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

①Bootstrap ClassLoader

負責加載$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類

②Extension ClassLoader

負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

③App ClassLoader

負責記載classpath中指定的jar包及目錄中class

④Custom ClassLoader

屬於應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader

加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。

(3)類執行機制

JVM是基於堆棧的虛擬機。JVM為每個新創建的線程都分配一個堆棧.也就是說,對於一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。

JVM執行class字節碼,線程創建後,都會產生程序計數器(PC)和棧(Stack),程序計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次調用,而棧幀又是有局部變量區和操作數棧兩部分組成,局部變量區用於存放方法中的局部變量和參數,操作數棧中用於存放方法執行過程中產生的中間結果。棧的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM內存管理及垃圾回收機制

JVM內存結構分為:方法區(method),棧內存(stack),堆內存(heap),本地方法棧(java中的jni調用),結構圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(1)堆內存(heap)

所有通過new創建的對象的內存都在堆中分配,其大小可以通過-Xmx和-Xms來控制。

操作系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣代碼中的delete語句才能正確的釋放本內存空間。但由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閒鏈表中。這時由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便。另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,它不是在堆,也不是在棧,而是直接在進程的地址空間中保留一塊內存,雖然這種方法用起來最不方便,但是速度快,也是最靈活的。堆內存是向高地址擴展的數據結構,是不連續的內存區域。由於系統是用鏈表來存儲的空閒內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大

(2)棧內存(stack)

在Windows下, 棧是向低地址擴展的數據結構,是一塊連續的內存區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是固定的(是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。只要棧的剩餘空間大於所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。 由系統自動分配,速度較快。但程序員是無法控制的。

堆內存與棧內存需要說明:

基礎數據類型直接在棧空間分配,方法的形式參數,直接在棧空間分配,當方法調用完成後從棧空間回收。引用數據類型,需要用new來創建,既在棧空間分配一個地址空間,又在堆空間分配對象的類變量 。方法的引用參數,在棧空間分配一個地址空間,並指向堆空間的對象區,當方法調用完成後從棧空間回收。局部變量new出來時,在棧空間和堆空間中分配空間,當局部變量生命週期結束後,棧空間立刻被回收,堆空間區域等待GC回收。方法調用時傳入的literal參數,先在棧空間分配,在方法調用完成後從棧空間收回。字符串常量、static在DATA區域分配,this在堆空間分配。數組既在棧空間分配數組名稱,又在堆空間分配數組實際的大小。

如:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(3)本地方法棧(java中的jni調用)

用於支持native方法的執行,存儲了每個native方法調用的狀態。對於本地方法接口,實現JVM並不要求一定要有它的支持,甚至可以完全沒有。Sun公司實現Java本地接口(JNI)是出於可移植性的考慮,當然我們也可以設計出其它的本地接口來代替Sun公司的JNI。但是這些設計與實現是比較複雜的事情,需要確保垃圾回收器不會將那些正在被本地方法調用的對象釋放掉。

(4)方法區(method)

它保存方法代碼(編譯後的java代碼)和符號表。存放了要加載的類信息、靜態變量、final類型的常量、屬性和方法信息。JVM用持久代(Permanet Generation)來存放方法區,可通過-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值。

垃圾回收機制

堆裡聚集了所有由應用程序創建的對象,JVM也有對應的指令比如 new, newarray, anewarray和multianewarray,然並沒有向 C++ 的 delete,free 等釋放空間的指令,Java的所有釋放都由 GC 來做,GC除了做回收內存之外,另外一個重要的工作就是內存的壓縮,這個在其他的語言中也有類似的實現,相比 C++ 不僅好用,而且增加了安全性,當然她也有弊端,比如性能這個大問題。

4、Java虛擬機的運行過程示例

上面對虛擬機的各個部分進行了比較詳細的說明,下面通過一個具體的例子來分析它的運行過程。

虛擬機通過調用某個指定類的方法main啟動,傳遞給main一個字符串數組參數,使指定的類被裝載,同時鏈接該類所使用的其它的類型,並且初始化它們。例如對於程序:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

編譯後在命令行模式下鍵入: java HelloApp run virtual machine

將通過調用HelloApp的方法main來啟動java虛擬機,傳遞給main一個包含三個字符串"run"、"virtual"、"machine"的數組。現在我們略述虛擬機在執行HelloApp時可能採取的步驟。

開始試圖執行類HelloApp的main方法,發現該類並沒有被裝載,也就是說虛擬機當前不包含該類的二進制代表,於是虛擬機使用ClassLoader試圖尋找這樣的二進制代表。如果這個進程失敗,則拋出一個異常。類被裝載後同時在main方法被調用之前,必須對類HelloApp與其它類型進行鏈接然後初始化。鏈接包含三個階段:檢驗,準備和解析。檢驗檢查被裝載的主類的符號和語義,準備則創建類或接口的靜態域以及把這些域初始化為標準的默認值,解析負責檢查主類對其它類或接口的符號引用,在這一步它是可選的。類的初始化是對類中聲明的靜態初始化函數和靜態域的初始化構造方法的執行。一個類在初始化之前它的父類必須被初始化。整個過程如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

最後,如果你打算學習java,小編已經為你備好了全套的java學習教程,希望對你有所幫助。

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

阿里架構師深度解析java技術原理,小白如何學習java到架構師

對於JVM的學習,在我看來這麼幾個部分最重要:

Java代碼編譯和執行的整個過程

JVM內存管理及垃圾回收機制

下面分別對這幾部分進行說明:

2、Java代碼編譯和執行的整個過程

也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(2)Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java代碼編譯和執行的整個過程包含了以下三個重要的機制:

·Java源碼編譯機制

·類加載機制

·類執行機制

(1)Java源碼編譯機制

Java 源碼編譯由以下三個過程組成:

①分析和輸入到符號表

②註解處理

③語義分析和生成class文件

流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

最後生成的class文件由以下部分組成:

①結構信息:包括class文件格式版本號及各部分的數量與大小的信息

②元數據:對應於Java源碼中聲明與常量的信息。包含類/繼承的超類/實現的接口的聲明信息、域與方法聲明信息和常量池

③方法信息:對應Java源碼中語句和表達式對應的信息。包含字節碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符號信息

(2)類加載機制

JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關係和加載順序可以由下圖來描述:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

①Bootstrap ClassLoader

負責加載$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類

②Extension ClassLoader

負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

③App ClassLoader

負責記載classpath中指定的jar包及目錄中class

④Custom ClassLoader

屬於應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader

加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。

(3)類執行機制

JVM是基於堆棧的虛擬機。JVM為每個新創建的線程都分配一個堆棧.也就是說,對於一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。

JVM執行class字節碼,線程創建後,都會產生程序計數器(PC)和棧(Stack),程序計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次調用,而棧幀又是有局部變量區和操作數棧兩部分組成,局部變量區用於存放方法中的局部變量和參數,操作數棧中用於存放方法執行過程中產生的中間結果。棧的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM內存管理及垃圾回收機制

JVM內存結構分為:方法區(method),棧內存(stack),堆內存(heap),本地方法棧(java中的jni調用),結構圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(1)堆內存(heap)

所有通過new創建的對象的內存都在堆中分配,其大小可以通過-Xmx和-Xms來控制。

操作系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣代碼中的delete語句才能正確的釋放本內存空間。但由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閒鏈表中。這時由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便。另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,它不是在堆,也不是在棧,而是直接在進程的地址空間中保留一塊內存,雖然這種方法用起來最不方便,但是速度快,也是最靈活的。堆內存是向高地址擴展的數據結構,是不連續的內存區域。由於系統是用鏈表來存儲的空閒內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大

(2)棧內存(stack)

在Windows下, 棧是向低地址擴展的數據結構,是一塊連續的內存區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是固定的(是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。只要棧的剩餘空間大於所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。 由系統自動分配,速度較快。但程序員是無法控制的。

堆內存與棧內存需要說明:

基礎數據類型直接在棧空間分配,方法的形式參數,直接在棧空間分配,當方法調用完成後從棧空間回收。引用數據類型,需要用new來創建,既在棧空間分配一個地址空間,又在堆空間分配對象的類變量 。方法的引用參數,在棧空間分配一個地址空間,並指向堆空間的對象區,當方法調用完成後從棧空間回收。局部變量new出來時,在棧空間和堆空間中分配空間,當局部變量生命週期結束後,棧空間立刻被回收,堆空間區域等待GC回收。方法調用時傳入的literal參數,先在棧空間分配,在方法調用完成後從棧空間收回。字符串常量、static在DATA區域分配,this在堆空間分配。數組既在棧空間分配數組名稱,又在堆空間分配數組實際的大小。

如:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(3)本地方法棧(java中的jni調用)

用於支持native方法的執行,存儲了每個native方法調用的狀態。對於本地方法接口,實現JVM並不要求一定要有它的支持,甚至可以完全沒有。Sun公司實現Java本地接口(JNI)是出於可移植性的考慮,當然我們也可以設計出其它的本地接口來代替Sun公司的JNI。但是這些設計與實現是比較複雜的事情,需要確保垃圾回收器不會將那些正在被本地方法調用的對象釋放掉。

(4)方法區(method)

它保存方法代碼(編譯後的java代碼)和符號表。存放了要加載的類信息、靜態變量、final類型的常量、屬性和方法信息。JVM用持久代(Permanet Generation)來存放方法區,可通過-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值。

垃圾回收機制

堆裡聚集了所有由應用程序創建的對象,JVM也有對應的指令比如 new, newarray, anewarray和multianewarray,然並沒有向 C++ 的 delete,free 等釋放空間的指令,Java的所有釋放都由 GC 來做,GC除了做回收內存之外,另外一個重要的工作就是內存的壓縮,這個在其他的語言中也有類似的實現,相比 C++ 不僅好用,而且增加了安全性,當然她也有弊端,比如性能這個大問題。

4、Java虛擬機的運行過程示例

上面對虛擬機的各個部分進行了比較詳細的說明,下面通過一個具體的例子來分析它的運行過程。

虛擬機通過調用某個指定類的方法main啟動,傳遞給main一個字符串數組參數,使指定的類被裝載,同時鏈接該類所使用的其它的類型,並且初始化它們。例如對於程序:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

編譯後在命令行模式下鍵入: java HelloApp run virtual machine

將通過調用HelloApp的方法main來啟動java虛擬機,傳遞給main一個包含三個字符串"run"、"virtual"、"machine"的數組。現在我們略述虛擬機在執行HelloApp時可能採取的步驟。

開始試圖執行類HelloApp的main方法,發現該類並沒有被裝載,也就是說虛擬機當前不包含該類的二進制代表,於是虛擬機使用ClassLoader試圖尋找這樣的二進制代表。如果這個進程失敗,則拋出一個異常。類被裝載後同時在main方法被調用之前,必須對類HelloApp與其它類型進行鏈接然後初始化。鏈接包含三個階段:檢驗,準備和解析。檢驗檢查被裝載的主類的符號和語義,準備則創建類或接口的靜態域以及把這些域初始化為標準的默認值,解析負責檢查主類對其它類或接口的符號引用,在這一步它是可選的。類的初始化是對類中聲明的靜態初始化函數和靜態域的初始化構造方法的執行。一個類在初始化之前它的父類必須被初始化。整個過程如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

最後,如果你打算學習java,小編已經為你備好了全套的java學習教程,希望對你有所幫助。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

"
阿里架構師深度解析java技術原理,小白如何學習java到架構師

一、Java 運行原理

1、高級語言運行過程

在程序真正運行在CPU上之前,必須要讓OS的kernel理解我們在編輯器或者IDE里根據每種語言的語法規則敲入的源代碼,kernel才能做出相關的調度,所以需要先將源代碼轉化成可執行的二進制文件,這個過程通常由編譯器完成。有些編譯器直接將源代碼編譯成機器碼,載入內存後CPU可以直接運行。而機器碼的格式與跟具體的CPU架構相關連,例如ARM CPU無法理解Intel CPU機器碼。因此,同樣的源代碼需要根據不同的硬件進行特定的編譯。高級語言轉換到低級語言的橋樑就是編譯器。程序員寫好源代碼,編譯器將源碼編譯成可執行的機碼,然後CPU讀取機器碼,執行程序。

2、Java語言的執行過程

阿里架構師深度解析java技術原理,小白如何學習java到架構師

寬泛地講,Java源代碼(.java)經過java編譯器(javac.exe)編譯之後,並沒有直接轉化為機器碼,而是轉化成一種中間格式——字節碼(.class),字節碼再經過Java虛擬機解釋,轉化成機器碼,然後經由操作系統到達CPU運行。整個執行過程如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java的跨平臺是基於JVM虛擬機這一中間物來實現的,Java源程序經過編譯器編譯後生成虛擬機能夠理解的字節碼(ByteCode——class文件的內容),虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定系統上的機器碼,然後在特定的機器上運行。每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM——Java Virtual Machine

JVM是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 的主要工作是解釋自己的指令集(即字節碼)並映射到本地的 CPU 的指令集或 OS 的系統調用。

三、 JVM的體系結構

Class Loader:類裝載器,從入口處開始按需加載.class文件,填充這些數據到運行時數據區

Execution Engine:執行引擎,JVM的CPU,不斷地取指令,JIT編譯翻譯執行字節碼,或者執行本地方法

Runtime Data Areas:運行時數據區,核心區,運行的時候操作所分配的內存區,包括方法區、堆、java棧、PC寄存器、本地方法棧

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1、類加載器

類加載器加載其實就是根據編譯後的Class文件,將Java字節碼載入JVM內存,並完成對運行數據處於的初始化工作,供執行引擎執行。

類加載過程:

裝載——鏈接(驗證,準備,解析)—— 初始化

阿里架構師深度解析java技術原理,小白如何學習java到架構師

1.Loading:,找到二進制字節碼(Class文件)並加載至JVM內存中,標識一個被加載的類:類名+類所在的包名+Class Loader instance ID

2.Linking:

  • Verifying:驗證元數據,文件格式,字節碼等,確保class文件包含的字節碼信息符合JVM的規範,以免危及JVM安全;
  • Preparing:準備分配給類所需要內存的數據結構,指示在類中定義的字段、方法和接口;
  • Resolving:對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限;符號引用的轉換等

3.Initialing:初始化執行類中的靜態初始化代碼、構造器代碼以及靜態屬性

類裝載器類型:

啟動類裝載器:JVM實現的一部分;

用戶自定義類裝載器:是Java程序的一部分,必須是ClassLoader類的子類。

類裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;應用進行Class Loader時,由User-Defined向Bootstrap方向查找並加載類;

類加載採用父類委託制,子加載器能查詢父加載器已緩存類,委託只能從下到上,反之不行。類加載器可以加載一個類,但是它不能卸載一個類。但是類加載器可以被刪除或者被創建。一個類可以被不同的類加載器加載。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Bootstrap ClassLoader

JVM的根ClassLoader,它是用C++實現的,在JVM啟動的時候創建,負責裝載$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件,這個jar中包含了Java規範定義的所有接口以及實現。

Extension ClassLoader

裝載除了基本的Java API以外的擴展類,它也負責裝載其他的安全擴展功能。

System ClassLoader

負責加載應用程序類,加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

User-Defined ClassLoader

Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

2、執行引擎

類加載器將.class文件載入內存之後,執行引擎以Java 字節碼指令為單元,讀取Java字節碼;而後由解釋器或者即時編譯器(JIT Compiler)將字節碼轉化成平臺相關的機器碼。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

JVM實現技術:

解釋器:第一代JVM,一條一條地讀取,解釋並且執行字節碼指令。因為它一條一條地解釋和執行指令,所以它可以很快地解釋字節碼,但是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種“語言”基本來說是解釋執行的。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

即時編譯器(just-in-time compiler):第二代JVM,狹義來說是當某段代碼即將第一次被執行時進行編譯,將class類文件解釋成二進制文件後的結果緩存下來,當第二次執行時直接從緩存中取,因此JIT依賴更多內存緩存解釋的結果。JIT編譯是動態編譯的一種特例。JIT編譯一詞後來被泛化,時常與動態編譯等價;但要注意寬泛與狹義的JIT編譯所指的區別。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

自適應編譯器(adaptive compiler):柔和第一代和第二代JVM,也是動態編譯的一種,但它通常執行的時機比JIT編譯遲,先讓程序“以某種形式”先運行起來,收集一些信息之後再做動態編譯,也就是說在所有執行過的代碼裡只尋找一部分來編譯;而”收集信息”決定了編譯哪部分代碼,換個角度說“收集信息”就是在程序運行過程中監控代碼執行的頻率,自動緩存利用率高的代碼,這樣的編譯可以更加優化。這個”某種形式”可以稱為“baseline execution“,可以由解釋器或簡單的JIT編譯器承擔。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

HotSpot是一個JVM的實現,得名於它得混合模式執行引擎(包括解釋器和自適應編譯器),這個JVM最初由Longview/Animorphic實現,隨著公司被Sun/JavaSoft收購而成為Sun的JVM,並於JDK 1.3.0開始成為Sun的Java SE的主要JVM。在Sun被Oracle收購後,現在HotSpot VM是Oracle的Java SE的主要JVM。HotSpot是較新的JVM,用來代替JIT(Just in Time), Java原先是把源代碼編譯為字節碼在虛擬機執行,這樣執行速度較慢;而HotSpot將最需要編譯的“熱點”代碼編譯為本地(原生native)代碼,如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,那麼Hotspot VM會把編譯過的本地代碼從cache裡移除,並且重新按照解釋的方式來執行它,這樣顯著提高了性能。 HotSpot VM 參數可以分為規則參數(standard options)和非規則參數(non-standard options)。Hotspot VM分為Server VM和Client VM兩種,這兩種VM使用不同的JIT編譯器。

3、運行時數據區

當運行一個JVM Instance時,系統將分配給它一塊內存區域(大小可設置),這一內存區域由JVM自行管理。從這一塊內存中分出一塊用來存儲一些運行數據,例如創建的對象,傳遞給方法的參數,局部變量,返回值等等。這一塊內存就稱為運行數據區域。運行數據區域可以劃分為6大塊:Java棧、程序計數寄存器(PC寄存器)、本地方法棧(Native Method Stack)、Java堆、方法區域(包括運行常量池——Runtime Constant Pool)。其中每個線程私有程序計數器,JVM棧,本地方法棧,方法區和堆則由JVM實例中的所有線程共享,在同一個實例中可以啟用多個線程。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

程序計數器

每個線程私有,線程啟動時創建,用來存放當前正在被執行的字節碼指令(JVM指令)的地址,如該方法為native的,則PC寄存器中不存儲任何信息。

JVM棧

每個線程私有,線程啟動時創建。存放著一系列的棧幀(Stack Frame),JVM只能進行壓入(push)和彈出(pop)棧幀這兩種操作。每當調用一個方法時,JVM就往棧裡壓入一個棧幀,方法結束返回時彈出棧幀。如果方法執行時出現異常,可用printStackTrace等方法來查看棧的情況。棧的示意圖如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

每個棧幀包含三個部分:本地變量數組,操作數棧,方法所屬類的常量池引用

  • Local Variable Array:從0開始按順序存放方法所屬對象的引用、傳遞給方法的參數、局部變量。
  • Operand Stack:存放方法執行時的一些中間變量,JVM在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。
  • Reference to Constant Pool:當JVM執行到需要常量池的數據時,就是通過這個引用來訪問常量池的。棧幀中的數據還要負責處理方法的返回和異常。如果通過return返回,則將該方法的棧幀從Java棧中彈出。如果方法有返回值,則將返回值壓入到調用該方法的方法的操作數棧中。另外,數據區中還保存中該方法可能的異常表的引用。

本地方法棧

當程序通過JNI(Java Native Interface)調用本地方法(如C或者C++代碼)時,就根據本地方法的語言類型建立相應的棧,此區域用於存儲每個native方法調用的狀態。

堆(Heap)

堆中存放的是程序創建的對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配。當堆中的空間無法滿足新建對象所需的內存開銷,會有溢出現象而導致程序崩潰,為了避免溢出,當對象執行結束時,其佔據的內存空間需要等待GC(Garbage Collection)進行回收,因此這個區域對JVM的性能影響很大。

注意: 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,導致了new對象的開銷是比較大的

Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區域

每個線程共享的,啟動一個JVM實例時被創建,它用於存運行放常量池、所加載的類的信息(域、方法、靜態變量、final類型的常量)。開發人員在程序中通過Class對象中的getName、isInterface等方法獲取的數據都來源於方法區域,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出Out Of Memory的錯誤信息。不同的JVM實現方式在實現方法區域的時候會有所區別。Oracle的HotSpot稱之為永久區域(Permanent Area)或者永久代(Permanent Generation)。

運行常量池

其空間從方法區域中分配,用來存放類、方法、接口的常量和域的引用信息,當一個方法或者域被引用的時候,JVM就通過運行常量池中的引用信息來查找方法和域在內存中的的實際地址。

四、JVM垃圾回收

Garbage Collection的基本原理:

將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停。

垃圾回收算法

1、按照基本回收策略分為以下4種:

Reference Counting:引用計數,比較古老的回收算法;原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,引用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。

Mark-Sweep:標記-清除,此算法執行分兩階段;第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Copying: 複製,把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題;但是此算法的缺點就是需要兩倍內存空間。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Mark-Compact:標記-整理,結合了Mark-Sweep和Copying兩個算法的優點;也分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。避免了Mark-Sweep算法的碎片問題,同時也避免了Copying算法的空間問題。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

2、按分區對待的方式分為以下2種

  • Incremental Collecting:增量收集,實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。JDK5.0中的收集器沒有使用這種算法的。
  • Generational Collecting:分代收集,基於對對象生命週期分析後得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

3、按系統線程分為以下3種

  • 串行收集:串行收集使用單線程處理所有垃圾回收工作,因為無需多線程交互,實現容易,而且效率比較高。但是,其侷限性是無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可以用在小數據量(100M左右)情況下的多處理器機器上。
  • 並行收集:並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現出並行收集器的優勢。
  • 併發收集:相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因為堆越大而越長。

處理碎片

由於不同Java對象存活時間是不一定的,因此,在程序運行一段時間以後,如果不進行內存整理,就會出現零散的內存碎片。碎片最直接的問題就是會導致無法 分配大塊的內存空間,以及程序運行效率降低。所以,在上面提到的基本垃圾回收算法中,“複製”方式和“標記-整理”方式,都可以解決碎片的問題。

對象創建和對象回收

垃圾回收線程是回收內存的,而程序運行線程則是消耗(或分配)內存的,一個回收內存,一個分配內存,從這點看,兩者是矛盾的。因此,在現有的垃圾回收方式 中,要進行垃圾回收前,一般都需要暫停整個應用(即:暫停內存的分配),然後進行垃圾回收,回收完成後再繼續應用。這種實現方式是最直接,而且最有效的解決二者矛盾的方式。

但是這種方式有一個很明顯的弊端,就是當堆空間持續增大時,垃圾回收的時間也將會相應的持續增大,對應應用暫停的時間也會相應的增大。一些對相應時間要求很高的應用,比如最大暫停時間要求是幾百毫秒,那麼當堆空間大於幾個G時,就很有可能超過這個限制,在這種情況下,垃圾回收將會成為系統運行的一個瓶頸。 為解決這種矛盾,有了併發垃圾回收算法,使用這種算法,垃圾回收線程與程序運行線程同時運行。在這種方式下,解決了暫停的問題,但是因為需要在新生成對象的同時又要回收對象,算法複雜性會大大增加,系統的處理能力也會相應降低,同時碎片問題將會比較難解決。

五、JRE(Java Runtime Environment)和JDK(Java Development Kit)

JRE是指運行Java程序所必須的環境集合,包含JVM標準實現及Java核心類庫。JDK 是 Java 語言的軟件開發工具包,針對Java開發員的產品,是整個Java的核心,包括了Java運行環境JRE、Java工具和Java基礎類庫。如果運行Java程序,只需安裝JRE就可以了。如果編寫Java程序,需要安裝JDK。OpenJDK則是包含了開發與運行的開源實現。最主流的JDK是Sun公司發佈的JDK,除了Sun之外,還有很多公司和組織都開發了屬於自己的JDK,例如IBM,阿里等。

根據應用領域的不同,JDK可分為三種版本:

SE(Standard Edition)標準版,通常用的一個版本,從JDK 5.0開始,改名為Java SE

EE(Enterprise Edition)企業版,使用這種JDK開發J2EE應用程序,從JDK 5.0開始,改名為Java EE

ME(Micro Edition)微型版,主要用於移動設備、嵌入式設備上的Java應用程序,從JDK 5.0開始,改名為Java ME

(轉)Java 詳解 JVM 工作原理和流程

作為一名Java使用者,掌握JVM的體系結構也是必須的。

說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

1、Java虛擬機的體系結構

·每個JVM都有兩種機制:

①類裝載子系統:裝載具有適合名稱的類或接口

②執行引擎:負責執行包含在已裝載的類或接口中的指令

·每個JVM都包含

方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器

阿里架構師深度解析java技術原理,小白如何學習java到架構師

對於JVM的學習,在我看來這麼幾個部分最重要:

Java代碼編譯和執行的整個過程

JVM內存管理及垃圾回收機制

下面分別對這幾部分進行說明:

2、Java代碼編譯和執行的整個過程

也正如前面所說,Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(2)Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

Java代碼編譯和執行的整個過程包含了以下三個重要的機制:

·Java源碼編譯機制

·類加載機制

·類執行機制

(1)Java源碼編譯機制

Java 源碼編譯由以下三個過程組成:

①分析和輸入到符號表

②註解處理

③語義分析和生成class文件

流程圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

最後生成的class文件由以下部分組成:

①結構信息:包括class文件格式版本號及各部分的數量與大小的信息

②元數據:對應於Java源碼中聲明與常量的信息。包含類/繼承的超類/實現的接口的聲明信息、域與方法聲明信息和常量池

③方法信息:對應Java源碼中語句和表達式對應的信息。包含字節碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符號信息

(2)類加載機制

JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關係和加載順序可以由下圖來描述:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

①Bootstrap ClassLoader

負責加載$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類

②Extension ClassLoader

負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

③App ClassLoader

負責記載classpath中指定的jar包及目錄中class

④Custom ClassLoader

屬於應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader

加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。

(3)類執行機制

JVM是基於堆棧的虛擬機。JVM為每個新創建的線程都分配一個堆棧.也就是說,對於一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。

JVM執行class字節碼,線程創建後,都會產生程序計數器(PC)和棧(Stack),程序計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次調用,而棧幀又是有局部變量區和操作數棧兩部分組成,局部變量區用於存放方法中的局部變量和參數,操作數棧中用於存放方法執行過程中產生的中間結果。棧的結構如下圖所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

3、JVM內存管理及垃圾回收機制

JVM內存結構分為:方法區(method),棧內存(stack),堆內存(heap),本地方法棧(java中的jni調用),結構圖如下所示:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(1)堆內存(heap)

所有通過new創建的對象的內存都在堆中分配,其大小可以通過-Xmx和-Xms來控制。

操作系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣代碼中的delete語句才能正確的釋放本內存空間。但由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閒鏈表中。這時由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便。另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,它不是在堆,也不是在棧,而是直接在進程的地址空間中保留一塊內存,雖然這種方法用起來最不方便,但是速度快,也是最靈活的。堆內存是向高地址擴展的數據結構,是不連續的內存區域。由於系統是用鏈表來存儲的空閒內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大

(2)棧內存(stack)

在Windows下, 棧是向低地址擴展的數據結構,是一塊連續的內存區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是固定的(是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。只要棧的剩餘空間大於所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。 由系統自動分配,速度較快。但程序員是無法控制的。

堆內存與棧內存需要說明:

基礎數據類型直接在棧空間分配,方法的形式參數,直接在棧空間分配,當方法調用完成後從棧空間回收。引用數據類型,需要用new來創建,既在棧空間分配一個地址空間,又在堆空間分配對象的類變量 。方法的引用參數,在棧空間分配一個地址空間,並指向堆空間的對象區,當方法調用完成後從棧空間回收。局部變量new出來時,在棧空間和堆空間中分配空間,當局部變量生命週期結束後,棧空間立刻被回收,堆空間區域等待GC回收。方法調用時傳入的literal參數,先在棧空間分配,在方法調用完成後從棧空間收回。字符串常量、static在DATA區域分配,this在堆空間分配。數組既在棧空間分配數組名稱,又在堆空間分配數組實際的大小。

如:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

(3)本地方法棧(java中的jni調用)

用於支持native方法的執行,存儲了每個native方法調用的狀態。對於本地方法接口,實現JVM並不要求一定要有它的支持,甚至可以完全沒有。Sun公司實現Java本地接口(JNI)是出於可移植性的考慮,當然我們也可以設計出其它的本地接口來代替Sun公司的JNI。但是這些設計與實現是比較複雜的事情,需要確保垃圾回收器不會將那些正在被本地方法調用的對象釋放掉。

(4)方法區(method)

它保存方法代碼(編譯後的java代碼)和符號表。存放了要加載的類信息、靜態變量、final類型的常量、屬性和方法信息。JVM用持久代(Permanet Generation)來存放方法區,可通過-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值。

垃圾回收機制

堆裡聚集了所有由應用程序創建的對象,JVM也有對應的指令比如 new, newarray, anewarray和multianewarray,然並沒有向 C++ 的 delete,free 等釋放空間的指令,Java的所有釋放都由 GC 來做,GC除了做回收內存之外,另外一個重要的工作就是內存的壓縮,這個在其他的語言中也有類似的實現,相比 C++ 不僅好用,而且增加了安全性,當然她也有弊端,比如性能這個大問題。

4、Java虛擬機的運行過程示例

上面對虛擬機的各個部分進行了比較詳細的說明,下面通過一個具體的例子來分析它的運行過程。

虛擬機通過調用某個指定類的方法main啟動,傳遞給main一個字符串數組參數,使指定的類被裝載,同時鏈接該類所使用的其它的類型,並且初始化它們。例如對於程序:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

編譯後在命令行模式下鍵入: java HelloApp run virtual machine

將通過調用HelloApp的方法main來啟動java虛擬機,傳遞給main一個包含三個字符串"run"、"virtual"、"machine"的數組。現在我們略述虛擬機在執行HelloApp時可能採取的步驟。

開始試圖執行類HelloApp的main方法,發現該類並沒有被裝載,也就是說虛擬機當前不包含該類的二進制代表,於是虛擬機使用ClassLoader試圖尋找這樣的二進制代表。如果這個進程失敗,則拋出一個異常。類被裝載後同時在main方法被調用之前,必須對類HelloApp與其它類型進行鏈接然後初始化。鏈接包含三個階段:檢驗,準備和解析。檢驗檢查被裝載的主類的符號和語義,準備則創建類或接口的靜態域以及把這些域初始化為標準的默認值,解析負責檢查主類對其它類或接口的符號引用,在這一步它是可選的。類的初始化是對類中聲明的靜態初始化函數和靜態域的初始化構造方法的執行。一個類在初始化之前它的父類必須被初始化。整個過程如下:

阿里架構師深度解析java技術原理,小白如何學習java到架構師

最後,如果你打算學習java,小編已經為你備好了全套的java學習教程,希望對你有所幫助。

阿里架構師深度解析java技術原理,小白如何學習java到架構師

阿里架構師深度解析java技術原理,小白如何學習java到架構師

獲取方式:請轉發+關注並私信小編:“資料”,即可獲取全套java自學教程哦!

"

相關推薦

推薦中...