編譯,魔數,常量池,字面量,數據表,堆棧,方法區,程序計數器,內存引用,內存溢出,垃圾回收器,新生區,永久區,指令集java
上一篇咱們介紹了代碼如何被翻譯成機器級程序,而後逐條送到CPU執行。可是現代硬件的指令集架構千差萬別,不一樣機器上運行相同代碼每每會出現指令集兼容問題。虛擬機在這個層面上把各類細節封裝好,提供通用的接口供上層應用調用。封裝好指令集架構的同時提供各類內存淘汰機制。本文將從宏觀及微觀角度來介紹類文件結構、虛擬機加載類文件機制,類文件生命週期及字節碼加載引擎,更加立體的加深對虛擬機工做的認識。算法
從咱們學習JAVA語言的第一天起,就執行過JAVA/JAVAC命令。JAVAC就是把咱們寫好的後綴爲.java的文本文件編譯成後綴爲.class的字節碼文件。上一章咱們介紹代碼本質的時候就瞭解到JAVA語言的語法元素。java文件咱們能夠經過文本編輯器打開,裏面也是咱們熟悉的java代碼,符合了java語言的語法規範。可是對於class裏面的內容,咱們要陌生不少。上一章咱們知道代碼經過編譯器翻譯成機器指令,那class文件會不會也是java虛擬機翻譯成的指令呢?編程
其實當java文件被編譯成class文件後,就跟java語言沒什麼關係了。指令執行引擎是JVM虛擬機,其餘編程語言,好比Scala,Python等均可以編譯成class文件,而後放到JVM來執行。這麼說來,咱們更加有必要探究class文件的本質了。api
咱們先從微觀的角度來介紹class文件的結構。先寫一個簡單的java文本文件,而後編譯成class文件,來觀察class的結構組成。數組
先定義一個接口文件,Add.java文件以下:瀏覽器
package com.lzh.jvm; public interface Add{ int add(int i,int j); }tomcat
再寫一個接口的實現類AddImpl.java,這個基本包含咱們平常常用的文件結構:安全
package com.lzh.jvm; public class AddImpl implements Add{ public static final int TOP = 100; private String point; public int add(int i,int j) { return i + j; } }
因爲存在包名定義咱們須要建好com/lzh/jvm的文件目錄,而後在當前目錄前後編譯com/lzh/jvm/Add.java文件和com/lzh/jvm/AddImpl.java文件。獲得了Add.class文件和AddImpl.class文件。多線程
Add.java二進制文件:架構
Add.class二進制文件:
AddImpl.java二進制文件:
AddImpl.class二進制文件:
以上四個圖是用WinHex二進制編輯工具打開的,左邊是文件的二進制編碼,右邊是ASCII標準編碼,因此只能表示英式鍵盤上的字符,出現中文的話則顯示亂碼。爲了閱讀方便,工具展現的是16進制的格式,兩個16進制的編碼表示一個字節空間(8位)。直觀上咱們能夠看出來java文件佔用的存儲空間比class要少不少,這也符合咱們上一章介紹的代碼翻譯過程。本質上計算機並不認識java文件裏面的內容,java屬於高級語言,裏面的語法更爲接近人類的語言,可是對於計算機來講全難以理解。因此須要把java文件的內容翻譯成jvm認識的文件格式。高級語言高度抽象了語言元素,翻譯爲機器指令則要花費更多的「口舌」來指導計算機一步步執行代碼語句。下一節咱們來解釋class文件的結構,從而理解jvm如何理解執行class的內容。
本節咱們將以上圖給的AddImpl.class爲例子來介紹類的結構。從結構上來看,class文件只存放兩種類型數據,分別爲基礎字段和表。
用於判斷文件類型,一般咱們以文件後綴來判別文件類型,可是若是修改後綴就會致使安全問題。class以4個字節的空間做爲開端,來標明class的類型,CA FE BA BE表示class類型的文件。
魔數後面緊接着4個字節表示jdk版本號。
常量池顧名思義是用於存放字符串常量,字符串常量包含:
咱們知道class本質是一些表的集合,一樣常量池也不例外,只不過存放在常量池位置的表有特定的類型,共有11種類型,以下表(圖片引用《深刻理解Java虛擬機 JVM高級特性與最佳實踐 》):
每一個表的表結構說明以下:
這11種類型的表第一個字段統一爲標誌字段tag,佔用u1一個字節,用於表示該表存放的數據類型。
首先進入常量池開始的兩個字節(u2)表示的是常量池的長度,也就是表的個數。
咱們能夠看到例子中常量池個數爲0x0017,轉換爲十進制爲23,因爲第0個表爲保留索引,表示沒引用到任何字符串,因此實際表的索引是從1開始計算,也就是1~23共22個表。
咱們先觀察AddImpl.class常量池,分析第1張表的表結構。查表可知緊接着表個數後面的u1位置爲0A,轉換爲十進制爲10,該表類型爲CONSTANT_Methodref_info,觀察表結構可知接下來的兩個u2位置屬於該表的字段,這兩個字段都是表索引類型,0x0003表示引用第3個表,0x0013表示引用第19個表。
而後該表結束緊接着是第2張表第一個表,該表tag爲07是CONSTANT_Class_info類型,第二個空間爲u2的字段值爲0x0014,引用第20個表。
接着分析第3張表,根據一樣的方法,一直能夠把常量池的表結構分析完。常量池的做用就是把源代碼全部文本數據都集中在常量池這個區間位置內,裏面各個表之間相互引用,統一管理文本數據。因爲表之間的引用,最後文本數據都是存放在CONSTANT_Class_info表裏面,而該表規定文本長度的字段length空間是u2類型,佔用2個字節,空間2的16次方,65536/1024=64K,因此java的變量或方法名大小不能超過64K。
修飾類或接口的限定標誌
在常量池結束後緊接着2個字節的訪問標誌,共32個標誌位。
類索引、父類索引與接口索引集合:指向常量池的CONSTANT_Class_info表,再由CONSTANT_Class_info表裏面的index指向特定CONSTANT_Utf8_info表的bytes字段的字面量。
字段表集合:
字段表結構以下
數組用 [ 表示,字段表用來表示類裏面全部變量(不包括方法裏面的局部變量)
方法表集合:
方法表結構以下
屬性表集合
方法體裏面的內容編譯爲Code屬性,code表結構以下
Code,Exceptions,LineNumberTable,LocalVariableTable,SourceFile,ConstantValue,InnerClasses,Deprecated,Synthetic
class文件就像是一個產品的模具,把模具製造出來的過程就是把class加載到jvm內存的過程,而後jvm再照着class模具的樣子印出對象來。重點在於模具的設計,其實模具被生產出來也是須要它自己有一套模具。這就是class嚴格的結構規範,class文件結構規範給出了各個方面的要求,只有按照這個要求造出來的模具纔是可用的,才能夠被用來製造產品,否則連產品線都上不去,就如同jvm判斷class不符合規範而拒絕加載。
類加載時機
類初始化的時機,大部分爲被動初始化,用不到的時候都不會初始化。
類加載過程
()方法,按源碼順序執行全部static的語句。沒有靜態變量或者static語句的類將不會有()。
類加載器
啓動類加載器,擴展類加載器,應用程序類加載器
類加載器採用雙親委派機制來讀取類文件,破壞雙親委派模型如:OSGI服務由自定義類加載器機制實現。每一個OSGI模塊(Bundle)都有本身的加載器
虛擬機性能監控與故障處理工具,給一個系統定位問題的時候,知識,經驗是基礎,數據是依據,工具就是處理數據的手段。
JDK的命令行工具
生成堆轉儲文件
用於生成虛擬機當前時刻的線程快照threaddump或者Javacore
JDK的可視化工具
本節從宏觀的角度講解JVM內存結構、內存分配運行策略,垃圾回收機制。
java內存區域與內存溢出
jvm內存區域:方法區,虛擬機棧,本地方法棧,堆,程序計數器;
內存回收概述:
虛擬機棧、本地棧和程序計數器在編譯完畢後已經能夠肯定所需內存空間,程序執行完畢後也會自動釋放全部內存空間,因此不須要進行動態回收優化。
jvm內存調優主要針對堆和方法區兩大區域的內存。
引用:強Strong,軟sfot,弱weak,虛phantom,強引用不會回收,軟引用在內存達到溢出邊界時回收,弱引用在每次回收週期時回收,虛引用專門被標記爲回收對象。
內存分配與回收策略
超過3m的對象直接進入老年區 -XX:PretenureSizeThreshold=3145728(3M)
Survivor區中的對象經歷一次Minor GC年齡增長一歲,超過15歲進入老年區
-XX:MaxTenuringThreshold=15
垃圾收集算法
標記-清除、複製、標記-整理、分代收集(新生用複製,老年用標記-整理)
運行時棧幀結構
每一個方法調用開始到執行完成的過程,對應這一個棧幀在虛擬機棧裏面從入棧到出棧的過程。
方法調用不等於方法執行,並且肯定調用方法的版本。
基於棧的字節碼解釋
解釋執行:
基於棧指令集與基於寄存器的指令集:
基於本地解釋器執行過程
類加載 執行子系統案例
tomcat類加載,OSGI熱插拔,字節碼生成技術,動態代理,Retrotranslator
程序編譯與代碼優化
早期編譯(編譯期)
重載要求方法具有不一樣的特徵簽名(不包括返回值),可是class文件中,只要描述不是徹底一致的方法就能夠共存,如:
public String foo(List<String> arg){ final int var = 0; return ""; } public int foo(List<Integer> arg){ int var = 0; return 0; }
晚期編譯(運行期)
解析模式 -Xint
編譯模式 -Xcomp
混合模式 Mixed mode
分層編譯:解釋執行 -> C1(Client Compiler)編譯 -> C2編譯(Server Compiler)
觸發條件:基於採樣的熱點探測,基於計數器的熱點探測
因爲JVM涉及內容較深且廣,篇幅有限沒法深刻分析細節。本文從微觀方面分析了做爲原材料的CLASS文件的結構,又從宏觀方面闡述了JVM是如何消化每個進入的CLASS。JVM自定義了一套邏輯上的指令集,這也呼應了以前咱們介紹的計算機如何運行一文,現代計算機性能有了長足的發展,可是本質上仍是完備的諾依曼體系架構。隨着量子計算的日新月異,相信將來的計算模型也會有革命性的突破。
最後,若是有想一塊兒學習Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式、高併發等架構技術的
能夠來一下個人QQ羣架構華山論劍:836442475【點擊進入】,好友都會在裏面交流,分享一些學習的方法和須要注意的小細節,天天準時講10年架構師分享經驗,Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式、高併發等架構技術