【實戰JVM】-基礎篇-01-JVM通識-字節碼詳解-類的聲明周期-加載器
- 1 初識JVM
- 1.1 什么是JVM
- 1.2 JVM的功能
- 1.2.1 即時編譯
- 1.3 常見JVM
- 2 字節碼文件詳解
- 2.1 Java虛擬機的組成
- 2.2 字節碼文件的組成
- 2.2.1 正確打開字節碼文件
- 2.2.2 字節碼組成
- 2.2.3 基礎信息
- 2.2.3.1 魔數
- 2.2.3.1 主副版本號
- 2.2.4 常量池
- 2.2.5 方法
- 2.3 linux中打開字節碼文件
- 2.4 字節碼常用工具 Arthas
- 2.4.1 安裝Arthas
- 2.4.2 Arthas功能
- 2.4.2.1 獲取系統實時面板-dashboard
- 2.4.2.2 加載特定類的字節碼-dump
- 2.4.2.3 反編譯已加載類的源碼-jad
- 2.4.2.4 查看JVM已加載的類信息-sc
1 初識JVM
1.1 什么是JVM
1.2 JVM的功能
1.2.1 即時編譯
即時編譯Just-In-Time 簡稱JIT進行性能的優化,最終到達接近C、C++的性能
將熱點代碼轉換為機器碼后保存至RAM,下次執行時直接從RAM中調用。
1.3 常見JVM
java -version
2 字節碼文件詳解
2.1 Java虛擬機的組成
2.2 字節碼文件的組成
2.2.1 正確打開字節碼文件
安裝jclasslib
打開任意一個class文件
2.2.2 字節碼組成
-
基礎信息(一般信息+接口):
- 魔數、字節碼對應的java版本號,訪問標識(public、final等),以及這個類父類是哪個,以及實現了哪些接口
-
常量池:
- 保存了字符串常量、類、接口名、字段名。主要在字節碼指令中使用。
-
字段:
-
當前類或接口的字段信息,包括名字,描述符,訪問標識。
-
private final static int a1=0
-
-
-
方法:
- 當前類或接口的聲明的方法信息字節碼指令
-
屬性:
- 類的屬性,比如源碼的名字、內部類的列表等
2.2.3 基礎信息
2.2.3.1 魔數
打開二進制的png文件,就是以89504E47開始的
jpg則以FFD8FF開始
java字節碼則是以CAFEBABE開始
2.2.3.1 主副版本號
52對應jdk1.8 61則對應jdk17
2.2.4 常量池
public class HelloWorld{public static final String a1= "a1a1a1";public static final String a2= "a1a1a1";public static void main(String[] args){System.out.println("Hello world!");}
}
查看編譯后的class文件
兩個都是常量,且指向同一塊常量值索引,CONSTANT_String_info存放著cp_info #33,依舊是個索引
查看cp_info #33,這時候字面量才是a1a1a1
為什么要兩級索引呢?這是因為在jvm中的運行時數據區域中有這方法區,方法區主要用來存儲已被虛擬機加載的類的信息、常量、靜態變量和即時編譯器編譯后的代碼等數據。方法區里有一個運行時常量池,用于存放靜態編譯產生的字面量和符號引用。該常量池具有動態性,也就是說常量并不一定是編譯時確定,運行時生成的常量也會存在這個常量池中。
public class HelloWorld{public static final String a1= "abc";public static final String a2= "abc";public static final String abc= "abc";public static void main(String[] args){System.out.println("Hello world!");}
}
比如字段名和常量名都叫abc,但常量名是abc是String類型,而字段名是無類型的,但是都指向utf8格式的abc
2.2.5 方法
public static void main(String[] args){int i=0;i=i++;System.out.println(i);
}
對應字節碼:
0 iconst_0 //操作數棧: [] -> [0],將常量值0壓入操作數棧。1 istore_1 //操作數棧: [0] -> [],將操作數棧頂的整數值(0)存入本地變量1。2 iload_1 //操作數棧: [] -> [0],將本地變量1中的整數值(0)加載到操作數棧。3 iinc 1 by 1 //本地變量1的值增加1。原值是0,現在變為1。6 istore_1 //操作數棧: [0] -> [],將操作數棧頂的整數值存入本地變量1。本地變量: [1]-> [0],7 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;> //獲取System.out的值并壓入操作數棧。
10 iload_1 //操作數棧: [Ljava/io/PrintStream;] -> [Ljava/io/PrintStream;, 0],將本地變量1中的整數值加載到操作數棧。
11 invokevirtual #3 <java/io/PrintStream.println : (I)V>
14 return
i處于局部變量表的1號位
如果換成++i
public static void main(String[] args){int i=0;i=++i;System.out.println(i);
}
0 iconst_01 istore_1 //0放到本地變量表2 iinc 1 by 1 //本地變量表先自增,0->15 iload_1 //將本地變量1中的整數值(1)加載到操作數棧。6 istore_1 //操作數棧: [1] -> [],將操作數棧頂的整數值存入本地變量1。本地變量: [1]-> [1],7 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
10 iload_1
11 invokevirtual #3 <java/io/PrintStream.println : (I)V>
14 return
這樣就不會發生i=i++這種覆蓋賦值的情況了。
作業:
int i=0,j=0,k=0;
i++;
j=j+1;
k+=1;
i和k一樣快,都是把0從操作數棧中放入本地變量中直接操作本地變量自增。
j最慢,從本地變量表中加載到操作數棧中,再加載1,再相加,再放入本地變量表。
2.3 linux中打開字節碼文件
javap -v 字節碼文件名稱
2.4 字節碼常用工具 Arthas
2.4.1 安裝Arthas
啟動arthas
先啟動項目再分析
public class ArthasDemo {public static void main(String[] args) {while (true) {try {Thread.sleep(2000L);} catch (InterruptedException e) {e.printStackTrace();}}}
}
在arthas工作目錄中啟動
java -Dfile.encoding=UTF-8 -jar arthas-boot.jar
-Dfile.encoding=UTF-8
是讓arthas以utf8格式啟動,這樣不會亂碼
成功捕獲到ArthasDemo,選擇3回車進入arthas內部,他還自動下載了arthas3.7.2版本
3
2.4.2 Arthas功能
2.4.2.1 獲取系統實時面板-dashboard
我們設置每隔兩秒刷新一次,刷新3次
dashboard -i 2000 -n 3
只顯示1次
2.4.2.2 加載特定類的字節碼-dump
dump -d D:/File/StudyJavaFile/JavaStudy/JVM/low/day01/resource/ com.sjb.arthas.ArthasDemo
這樣就獲取了運行時的java文件的字節碼信息
2.4.2.3 反編譯已加載類的源碼-jad
jad com.sjb.arthas.ArthasDemo
和我們的源碼幾乎一致
案例
啟動springboot-classfile后
public UserVO user(@PathVariable("type") Integer type,@PathVariable("id") Integer id){//前邊有一大堆邏輯,巴拉巴拉if(type==UserType.REGULAR.getType()){return new UserVO(id,"普通用戶無權限查看");}return new UserVO(id,"這是尊貴的收費用戶才能看的秘密!");
}
不能用==來判斷類型,需要equals
即使是普通用戶,但是因為用的==判斷的類型,也能進入vip用戶
使用jad查看
jad com.itheima.springbootclassfile.controller.UserController
定位到問題信息,以供以后熱更新
2.4.2.4 查看JVM已加載的類信息-sc
sc -d 類名(java.lang.String)
查看當前類的類加載器,如果為空,則為啟動類加載器。