JVM虛擬機個人總結(二)

今天來總結一下這個星期所學到的關於JVM的知識。

這星期我看了JVM的內部的概述,先用一張圖來進行展示說明:
這裏寫圖片描述

下面我會逐個進行概述(只是大概瞭解每個模塊的功能,後面會進行後面的總結會進行詳細的瞭解)。

好,在概述這些模塊之前,首先需要了解的是Java虛擬機的生命週期?
Java虛擬機內部有兩種線程,一種是守護線程,另一種是非守護線程。守護線程時Java虛擬機內部自己使用的,比如垃圾回收線程。但是Java程序也可以把任何線程標記爲守護線程。非守護線程main()方法的那個線程,只要還有任何非守護線程還在運行,那這個Java程序也在繼續運行。當改程序中的所有非守護線程都終止了,Java虛擬機實例也將自動退出。
接下來需要了解一下Java數據類型是什麼?
Java數據類型分爲兩種:基本類型和引用類型。基本類型的變量持有原始值,是真正的原始數據,而引用類型的變量持有是對某個對象的引用,而不是對象本身。
看下面這張圖:這裏寫圖片描述
這裏寫圖片描述
還有一個類型只在內部使用:returnAddress,是用來實現Java程序中的finally子句的,程序員無法調用這個基本類型。
Java有三種引用類型統稱爲引用類型:類類型、接口類型以及數組類型,它們都是對動態創建對象的引用。
Java虛擬機還有一個字的概念。在Java虛擬機中最基本的數據單元是字,它的大小是由每個虛擬機實現的設計者來決定的。字長一般需要能夠持有byte,short等基本類型,也就是它們至少選擇32位作爲字長。(這一塊我就大致瞭解有這個一個概念)

下面就真正開始概述虛擬機下各個模塊。
首先先來講類裝載子系統。Java虛擬機有兩種類裝載器:啓動類裝載器和用戶自定義裝載器。
裝載的順序:
1)裝載——查找並裝載類型的二進制數據
2)連接——執行驗證、準備以及解析(可選)
1.驗證——確保被導入類型的正確性
2.準備——爲類變量分配內存,並將其初始化爲默認值
3.解析——把類型中的符號引用轉化爲直接引用
3)初始化——把類變量初始化爲正確的初始值

接下來講方法區。被Java虛擬機裝載的class文件類型信息存儲在方法區中,也就是說方法區存儲關於類的信息,也包括class的static靜態信息。
由於所有線程共享方法區,所以他們對方法區數據的訪問必須是線程安全的,也就是說假設兩個線程訪問同一個類,只有一個線程能夠去加載它,另一個線程只能等待。方法區也可以被垃圾回收。
虛擬機都會在方法區中存儲以下信息:
1.這個類型的全限定名
2.這個類型的直接超類的全限定名(除非這個類是java.lang.Object這個類沒有超類)
3.這個類型是類類型還是接口類型
4.這個類型的訪問修飾符
5.任何直接超接口的全限定名的有序列表
除了上面列出的基本信息,還有
1.該類型的常量池
2.字段信息
3.方法信息
4.除了常量以外的所有類靜態變量
5.一個到類ClassLoader的引用
6.一個到類Class類的引用

常量池:虛擬機必須爲每個被裝載的類型維護一個常量池,是該類型所用常量的一個有序集合,包括直接常量(String,Integer等常量),還有對其他類型,字段和方法的引用,是通過數組一樣用索引進行訪問的。

字段信息:對於類型中聲明的每一個字段的字段名、字段的類型、字段的修飾符。

方法信息:對於類型中聲明的每一個方法的方法名,方法的返回類型、方法參數的數量和類型(按聲明的順序)、方法的修飾符。如果這個方法不是抽象或本地的,還需要記錄方法的字節碼、操作數棧和該方法的棧幀中的局部變量區的大小、異常表(這些名詞後面都會解釋)

類靜態變量:這些變量只與類有關,跟類的實例無關

指向ClassLoader的引用:虛擬機必須跟蹤類是由啓動類裝載器還是由用戶自定義裝載器裝載的,這是作爲方法表中類型數據的一部分保存的。

指向Class類的引用:對於一個被裝載的類型(不管是類還是接口),虛擬機都會相應地爲它創建一個java.lang.Class類的實例,而且虛擬機必須以某種方式把這個實例和存儲在方法區中的類型數據關聯起來。

方發表:爲了提高訪問效率的一種數據結構,虛擬機對每個裝載的非抽象類都生成一個方法表,它是一個數組,它的元素都是它的實例可以被調用的實例方法的直接引用,包括從那些超類繼承過來的實例方法,運行時可以通過方法錶快速搜尋在對象中調用的實例方法。

下一個介紹的是堆。Java程序在運行時創建的所有類的實例或數組都放在同一個堆中。一個Java虛擬機只存在一個堆空間,所有線程都共享這個堆,又由於一個Java程序獨佔一個Java虛擬機實例,因而每個Java程序都有自己的堆空間——它們不會彼此干擾。但是同一個Java程序的多個線程共享一個堆空間,所以要注意同步問題。
多態的實現:當程序運行時需要轉化爲對某個對象引用爲另一種類型時,虛擬機必須檢查這種轉化是否被允許,被引用的對象會通過方法區查看類信息,確定是否被轉化的類型或者是超類型,但是在調用某個實例方法時,虛擬機會進行動態綁定,換句話說也就是它不能按照引用的類型來決定將要調用的方法,而必須根據對象的實際類型。所以,虛擬機必須再次通過對象的引用去訪問方法區中的類數據。
堆和方法表的實現:這裏寫圖片描述

下一個是程序計數器(這塊我感覺無需太瞭解)。對於一個運行中的Java程序而言,每一個線程都有一個自己的PC(程序計數器)寄存器,它是在該線程啓動時創建的,當線程執行某個Java方法時,PC寄存器的內容總是下一條將要被執行指令的“地址”。

下面介紹Java棧。每當啓動一個新線程時,Java虛擬機都會爲它分配一個Java棧,Java棧以幀爲單位保存在線程的運行狀態,虛擬機只會對Java棧進行兩種操作:以幀爲單位的壓棧和出棧。
某個線程正在執行的方法被稱爲該線程的當前方法,當前方法使用的棧稱爲當前棧……
每當線程調用一個一個Java方法時,虛擬機都會在該線程中壓入一個新幀,而這個新幀自然也就是當前幀,在執行這個方法時,它用這個幀來存儲參數、局部變量、中間運算結果等數據。
Java方法有兩種方式完成,一個是return,一個是拋異常,都會將當前幀釋放掉,上一個幀就變成了當前幀。
Java棧上的所有數據是私有的,任何線程不能訪問另一個線程的棧數據。因此不需要考慮多線程情況下的同步問題,當一個線程調用一個方法時,方法的局部變量保存在調用線程的Java棧中,只有調用方法的線程能夠訪問那些局部變量。

下一個是重點:Java棧幀。棧幀有三部分組成:局部變量區、操作數棧、幀數據區。
當虛擬機調用一個Java方法時,它從對應的類的類型信息中得到此方法的局部變量區和操作數棧的大小,並據此分配棧幀內存,然後壓入到Java棧中。
局部變量區以字長爲單位,從0開始計數的數組,類型爲int、float、referece和returnAdress的值在數組中只佔一項,而類型爲byte、short、char的值在存入數組前都將被轉化爲int,所以也只佔據一項,而long、double的值佔據連續的兩項。
局部變量區包含對應方法的參數和局部變量,編譯器首先按聲明的順序把這些參數放入局部變量數組,看下面兩個例子:
public static int runClassMethod(int i,long l, float f,double d ,Object o ,byte b){
return 0;
}
public int runInstanceMethod(char c,double d, short s,double d ,boolean b){
return 0;
}
這裏寫圖片描述
注意在runInstanceMethod()中,局部變量第一個參數爲reference,儘管在源代碼中沒有顯式聲明這個參數,但是這個參數this對應任何一個實例方法都是隱含加入的,它用來標識調用方法的對象本身。而char等被轉化成了int的參數。在存回堆或方法區時纔會轉回原來的類型。除參數外,Java方法內的局部變量可以按照任意順序,當出現作用域不同時,比如for循環定義的i和j可以共用一個索引,比如
這裏寫圖片描述

操作數棧也是一個組織成一個以字長爲單位的數組,但是它是通過標準的棧操作——壓棧和出棧進行訪問的。基本類型的存儲方式跟之前的相同。
Java虛擬機沒有寄存器(這塊我也不懂,不是之前說PC計數器是寄存器麼),Java虛擬機的指令主要是從操作數棧中而不是從寄存器中取得操作數的,因此它的運行是基於棧的而不是基於寄存器的。
虛擬機把操作數棧作爲它的工作區,比如執行加法就需要:
這裏寫圖片描述
這裏寫圖片描述

幀數據區:Java棧幀還需要一些數據來支持常量池的解析、正常方法的返回以及異常派發機制,這些信息都保存在Java棧幀的幀數據區中。
每當虛擬機要執行某個需要用到常量池的指令時,它都會通過幀數據區中指向常量池的zh指針來訪問它。常量池中堆類型、字段和方法的引用在開始時都是符號,當虛擬機在常量池搜索時,如果遇到指向類、接口、字段或者方法的入口,假若它們仍然是符號,纔會進行解析。
除了常量池的解析,幀數據區還要幫助虛擬機處理Java方法的正常結束和異常終止,若是return無返回值正常結束,則虛擬機必須回覆發起調用方法的棧幀,包括設置PC寄存器指向發起調用的方法中的指令+即緊跟着調用了完成方法指令的下一個指令。加入有返回值,虛擬機必須將它壓入到發起調用的方法的棧幀中的操作數棧。
當處理異常情況時,幀數據還必須保存一個對此方法異常表的引用,定義了在這個方法的字節碼中收catch子句保護的範圍,異常表中的每一項都有一個被catch子句保護的代碼的起始和結束位置(即try子句內部的代碼),當某個方法拋出異常時,虛擬機根據幀數據區對應的異常表來決定如何處理。如果在異常表中找到了匹配的catch子句,就會把控制權轉交給catch子句中的代碼,如果沒有發現,方法會立即異常中止,然後虛擬機使用幀數據區的信息恢復發起調用的方法的幀,然後在發起調用的方法的上下文重新拋出同樣的異常。
這裏寫圖片描述
這裏寫圖片描述

接下來講本地方法棧。當線程調用Java方法時,虛擬機會創建一個新的棧幀並壓入Java棧,當它調用的是本地方法時,虛擬機會保持Java棧不變,不在線程的Java棧中壓入新的幀,只是簡單的動態連接並直接調用指定的本地方法。
這裏寫圖片描述
該線程首先調用了兩個Java方法,第二個Java方法調用了一個本地方法,這樣導致虛擬機使用了一個本地方法棧。假設這是一個C語言棧,其中有兩個C函數,第一個C函數被第二個Java方法當做本地方法調用,而這個函數又調用了第二個C函數,之後第二個C函數又通過本地方法接口回調了一個Java方法,最終這個Java方法又調用了一個Java方法。

之後講執行引擎。運行中Java程序的每一個線程都是一個獨立的虛擬機執行引擎的實例。從線程生命週期的開始到結束,它要麼在執行字節碼,要麼在執行本地方法。Java虛擬機的實現可能用一些用戶程序不可見的線程,比如垃圾收集器,這樣的線程不需要是實現的執行引擎的實例,所有屬於用戶運行程序的線程,都是在實際工作的執行引擎。

關於本地方法接口,個人暫不學習,所以不做總結。

今天的分享到此結束。致謝正在向光奔跑的自己。

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