Categories
程式開發

深入java week1-01 字節碼、內存、GC、調試工具


1. java字節碼技術

1.1 什麼是字節碼?

字節碼(Java bytecode),是由Java編譯器把Java代碼轉換後,可以由java虛擬機無腦執行的指令集。也是java跨平台的核心所在。 Java維護者(組織)為所有主流操作系統提供了一個Java虛擬機,這些虛擬機向上可以識別java字節碼,向下則適配本地環境,執行字節碼裡面的指令,在轉換成cpu執行指令。

它是程序的一種低級表示,可以運行於Java虛擬機上。將程序抽象成字節碼可以保證Java程序在各種設備上的運行。計算機裡面的很多事情問題都可以通過增加一個中間層來解決,很顯然,字節碼+JVM就是這麼一個中間層,解決跨平台的問題。

Java bytecode由單字節(byte)的指令組成,理論上最左支持256個操作碼(opcode)。實際上Java只是用了200個左右的操作碼,還有一些操作碼則保留給調試操作。

根據指令的性質,主要分為四個大類:

棧操作指令,包括與局部變量交互的指令Java虛擬機(JVM)是​​一個基於棧的計算機。所有的計算都發生在棧上。程序流程控制指令程序的流程控制指令,比如,for,if,函數調用對像操作指令,包括方法調用指令java是一個面向對象的語言,創建一個對象,調用對象的方法。算數運算以及類型轉換指令。

1.2 查看字節碼

代碼

深入java week1-01 字節碼、內存、GC、調試工具 1

查看字節碼

javac xxx.java 生成class文件javap -c xxx.class 查看字節碼

深入java week1-01 字節碼、內存、GC、調試工具 2

實際上,class文件裡面保存的都是字節碼,0到255的數字,上圖展示的是助記符,方便記憶和閱讀用的。 aload_0,return等都有自己對應的操作碼的。

javap -c -verbose xxx.class 查看更信息的字節碼信息

深入java week1-01 字節碼、內存、GC、調試工具 3

上圖中,有jdk的版本號,類的屬性(public),還有常量表。代碼行號表(調試的時候可以看到指令對於的行號)。

執行流程

深入java week1-01 字節碼、內存、GC、調試工具 4

深入java week1-01 字節碼、內存、GC、調試工具 5

執行的時候,從常量表中獲取到常量值,在放到程序棧(變量表)中盡心計算。

2. 類加載

2.1 類的生命週期

加載(Loading):找class文件,並讀入程序內存中通過類名com.xxx.Class從各種classpath目錄裡面找到對應類,也可以自定義類加載器,從網絡上加載類或jar包。驗證(Verification): 驗證格式,依賴驗證格式是否正確。版本號。類之間的相互引用關係。準備(Preparation): 靜態字段,方法表抽取類裡面的靜態字段抽取類裡面的方法。搭建類的結構(骨架)解析(Resolution): 符號解析為引用把各種符號替換成引用。初始化(Initialization): 構造器,靜態變量賦值,靜態代碼靜態變量賦值靜態代碼執行然後這個類就可以創建實例了。可以被使用了。使用(Using): 創建類實例,並使用卸載(Unloading): 清除類的信息

2.2 什麼時候會加載類,會初始化類

當虛擬機啟動時,初始化用戶指定的主類。 (main方法所在的類)當遇到用以新建目標類實例的new指令時,初始化new指令的目標類,就是new一個類的時候要初始化。 (創建類的實例,那肯定需要類被加載了。)當遇到調用靜態方法的指令時,初始化該靜態方法所在的類。當遇到訪問靜態字段的指令時,初始化該靜態字段所在的類。子類的初始化會觸發父類的初始化。如果一個接口定義了default方法,那麼直接實現或間接實現該接口的類的初始化,會觸發該接口的初始化。使用反射API對某個類進行反射調用時,初始化這個類,反射調用要么是已經有實例了,要么是靜態方法,都需要初始化。當初次調用MethodHandle實例時,初始化該MethodHandle指向的方法所在的類。

2.3 什麼時候會加載類,不會初始化類

通過子類引用父類的靜態字段,只會觸發父類的初始化,而不會觸發子類的初始化。但子類肯定是被加載了的。定義對像數組,不會觸發類的初始化。數組只是一個聲明,實際上還沒有創建對象呢。常量在編譯期間會存入調用類的常量池中,本質上並沒有直接一用定義常量的類,不會觸發定義常量所在的類。比如:java字符串字面量”xxxx”其實就是一個String常量了。但是並不會觸發String類的初始化。通過類名獲取Class對象,不會觸發類的初始化。 Hello.Class不會讓Hello類初始化。通過Class.forName加載指定類時,如果指定參數initialize為false時,不會觸發類初始化,其實這個參數是告訴虛擬機,是否需要對類進行初始化。 Class.forName(“jvm.Hello”)默認會初始化Hello類。通過ClassLoader默認的loadClass方法,不會觸發初始化動作(類加載了,但是不初始化)

所謂加載類,不初始化類指的就是,會從class文件裡面加載類的字節碼,但是並不會執行靜態字段的賦值,靜態代碼塊的執行。

2.4 類加載器

啟動類加載器(BootstrapClassLoader)加載JVM啟動的核心系統類擴展類加載器(ExtClassLoader)擴展的類,也是在jdk裡面自帶的。應用類加載器(AppClassLoader)加載程序員寫的代碼,jar包。

怎麼保證類不會重複加載?雙親委託:應用類加載器在加載類的時候,會先去擴展類加載器裡面找類是否已被加載,如果沒有就去啟動類加載器找,如果還沒有,則自己加載類。負責依賴:加載一個類的時候,還需要把這個類依賴的其它類也給加載進來。緩存加載:類被加載完後,會緩存起來。自定義類加載器自定義類加載器加載出來的類是不一樣的,哪怕都是從同一個class文件加載進來的類,但是實例是不能相互類型轉換的。因為沒有共同的上一級加載器。 java中類其實也是一個對象,不同的加載器加載的類,就相當於2個類對象,雖然對象的內容是一樣的,但是地址什麼的就不一樣了(比喻)。基於這個特性,可以加載不同版本的類,解決類兼容的問題,比如引入了一個外部工具,這個工具依賴了xxx.class 1.0.0。但是現有的代碼也依賴的卻是xxx.class 1.1.0這樣就勢必要加載2個版本的xxx.class了。那麼自定義類加載器就派上用場了。

深入java week1-01 字節碼、內存、GC、調試工具 6

2.5 添加引用類的幾種方式(就是發現類)

把類/jar放到JDK的lib/ext下,或者-Djava.ext.dirs指定類查找路徑java-cp/classpath或者class文件放到當前路徑自定義ClassLoader加載。這個就比較靈活了,可以從網絡上下載一個類。拿到當前執行類的ClassLoader,反射調用addUrl方法添加jar或者路徑。說白了,還是添加路徑其他的類加載器才能找到類、jar包。 (JDK9就不能用這種方法了,提供了新方法。)

3. JVM內存模型

4. JDK內置的命令行工具

4.1 工具功能展示

4.1.1 jps / jinfo

查看當前系統啟動的java進程。就跟linux的ps命令一樣,只是jps只顯示java進程jps -mlv :查看更詳細的信息。 jvm的啟動參數,垃圾回收算法等。

4.1.2 jstat

jstat -gc 98800 1000 20 查看進程98800的內存,和gc情況,1000表示每秒刷新一次,顯示20次。 s0c存活區0的容量,s0u表示存活區0使用的內存數。 EC表示伊甸區的容量。 OC老年區的容量。 MC表示元數據區的容量。 YGC表示youngGC次數。 YGCT表示youngGC的總時間。 FGC全量垃圾回收的冊數,FGCT表示全量GC的總時間。單位都是字節。

深入java week1-01 字節碼、內存、GC、調試工具 7

jstat -gcutil 98800 1000 20: 查看各個區域內存的使用率。百分比

4.1.3 jmap 查看更詳細的jvm信息,jvm裡面的所有對象,以及對像個數,使用的字節數。如果某個對象特別多。可能就是內存洩漏了。

jmap -histo pid

深入java week1-01 字節碼、內存、GC、調試工具 8

jmap -heap 14068 查看堆內存信息

深入java week1-01 字節碼、內存、GC、調試工具 9

4.1.4 jstack

jstack pid:查看jvm所有線程的棧。

4.1.5厘米

jcmd是一個比較綜合的命令行工具。和上面的那些都是職責單一的。 jcmd 14068 help :查看指定進程,支持哪些工具。

深入java week1-01 字節碼、內存、GC、調試工具 10

jcmd pid Thread.print: 查看jvm的所有線程棧。

4.1.6 jrunscript / jjs

執行js腳本命令。

5. JDK內置圖形化工具

說明:功能其實和命令行工具差不多,只是有窗口界面,比較方便。

5.1控制台

圖形化顯示JVM內存,線程,cpu的使用情況。

深入java week1-01 字節碼、內存、GC、調試工具 11

5.2 jvisualvm

抽樣統計功能,比jconsole更好用一點。更強大一點。可以查看一段時間(單位時間)系統的狀態。

深入java week1-01 字節碼、內存、GC、調試工具 12

5.3吉姆

死鎖都能檢測出來。