文章目錄
a、線程共享:所有線程能訪問這塊內存數據,隨虛擬機或者GC而創建和銷燬
b、線程獨佔:每個線程都會有它獨立的空間,隨線程生命週期而創建和銷燬
3、使用javap命令將解析結果說明放到Demo1.txt文件中
一、前言
- 重點
- 線程安全概念、線程通信的方式及應用、reactor線程模型、 關於線程數量的優化、JDK的常用命令、Netty框架的作用
- 難點
- JAVA程序運行的原理、同步關鍵字的原理、AQS的抽象、JUC的源碼、網絡編程的概念、理解GC機制
- 技巧
- 通過寫一段申請內存C代碼,理解JVM內存和運行程序的概念;
- 將所學內容前後串連起來,形成java的知識體系;
- 跟着操作,然後自己做筆記;
- 嘗試將內容和其他人溝通;
二、class文件內容
class文件包含JAVA程序執行的字節碼;數據嚴格按照格式緊湊排列在class文件中的二進制流,中間無任何分隔符;文件開頭有一個0xcafebabe(16進制)特殊的一個標誌。
下圖展示爲16進制
他包含了
- 版本
- 訪問標誌
- 常量池
- 當前類
- 超級類
- 接口
- 字段
- 方法
- 屬性
這文件是有複雜格式,專門給JVM讀裏面的內容,人類閱讀可以藉助工具查看;
三、JVM運行時數據區
JVM運行時會爲class字節碼文件分配對應區域去存儲信息,而這片區域又分很多的數據區域數據區域分爲線程共享部分和線程獨佔部分
a、線程共享:所有線程能訪問這塊內存數據,隨虛擬機或者GC而創建和銷燬
1、方法區
方法區:JVM用來存儲加載的類信息、常量、靜態變量、編譯後的代碼等數據虛擬機規範中這是一一個邏輯區劃(邏輯區劃:就 是沒有硬性去規定你怎麼實現,具體實現根據不同虛擬機來實現)
如: oracle的HotSpot在java7中 方法區放在永久代,java8放在元數據空間,並且通過GC機制對這個區域進行管理
2、堆內存
堆內存:堆內存可以細分爲:老年代 、新生代(Eden、 From Survivor、To Survivor),JVM啓動時創建,存放對象的實例。垃圾回收器主要就是管理堆內存。如果滿了,就會出現OutOfMemroyError,後續在內存模型中,詳細講解。
b、線程獨佔:每個線程都會有它獨立的空間,隨線程生命週期而創建和銷燬
1、虛擬機棧
虛擬機棧:
每個線程都在這個空間有一個私有的空間。線程棧由多個棧幀(Stack Frame)組成。
一個線程會執行一個或多個方法,一個方法對應一個棧幀
棧幀內容包含:局部變量表、操作數棧、動態鏈接、方法返回地址、附加信息等。
棧內存默認最大是1M,超出則拋出StackOverflowError
2、本地方法棧
本地方法棧:
和虛擬機棧功能類似,虛擬機棧是爲虛擬機執行JAVA方法而準備的,本地方法棧是爲虛擬機使用Native本地方法而準備的。
虛擬機規範沒有規定具體的實現,由不同的虛擬機廠商去實現。
HotSpot虛擬機中虛擬機棧和本地方法棧的實現式一-樣的。同樣,超出大小以後也會拋出StackOverflowError
3、程序計數器
程序計數器:程序計數器(Program Counter Register)記錄當前線程執行字節碼的位置,存儲的是字節碼指令地址,如果執行Native方法,則計數器值爲空。
每個線程都在這個空間有一個私有的空間,佔用內存空間很少。
CPU同- -時間,只會執行一條線程中的指令。 JVM多線程會輪流切換並分配CPU執行時間的方式。爲了線程切換後,
需要通過程序計數器,來恢復正確的執行位置。
四、使用javap命令查看class文件的內容
1、新建Demo1類
public class Demo1{ public static void main(String[] args){ int x = 500; int y = 100; int a = x / y; int b = 50; System.out.println(a + b); } }
2、使用javac命令將類文件編譯成class文件
javac Demo1.java
3、使用javap命令將解析結果說明放到Demo1.txt文件中
javap -v Demo1.class>Demo1.txt
下面開始查看Demo1編譯成的class文件包含了什麼內容
Demo1.txt內容如下:
Classfile /ideaWorkspace/git/subject-1/subject-1-docs/Demo1.class Last modified 2019-8-30; size 414 bytes MD5 checksum ae6fa820973681b35609c75631cb255b Compiled from "Demo1.java" public class Demo1 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #5.#14 // java/lang/Object."<init>":()V #2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream; #3 = Methodref #17.#18 // java/io/PrintStream.println:(I)V #4 = Class #19 // Demo1 #5 = Class #20 // java/lang/Object #6 = Utf8 <init> #7 = Utf8 ()V #8 = Utf8 Code #9 = Utf8 LineNumberTable #10 = Utf8 main #11 = Utf8 ([Ljava/lang/String;)V #12 = Utf8 SourceFile #13 = Utf8 Demo1.java #14 = NameAndType #6:#7 // "<init>":()V #15 = Class #21 // java/lang/System #16 = NameAndType #22:#23 // out:Ljava/io/PrintStream; #17 = Class #24 // java/io/PrintStream #18 = NameAndType #25:#26 // println:(I)V #19 = Utf8 Demo1 #20 = Utf8 java/lang/Object #21 = Utf8 java/lang/System #22 = Utf8 out #23 = Utf8 Ljava/io/PrintStream; #24 = Utf8 java/io/PrintStream #25 = Utf8 println #26 = Utf8 (I)V { public Demo1(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=5, args_size=1 0: sipush 500 3: istore_1 4: bipush 100 6: istore_2 7: iload_1 8: iload_2 9: idiv 10: istore_3 11: bipush 50 13: istore 4 15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 18: iload_3 19: iload 4 21: iadd 22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 25: return LineNumberTable: line 3: 0 line 4: 4 line 5: 7 line 6: 11 line 7: 15 line 8: 25 } SourceFile: "Demo1.java"
4、版本信息
public class Demo1 minor version: 0 //次版本號 major version: 52 //主版本號 flags: ACC_PUBLIC, ACC_SUPER //訪問標誌
版本號的規則:JDK5、6、7、8分別對應49、50、51、52 如上面52對應的就是JDK8
flags 訪問標誌:類的修飾符
標誌的名稱對應含義如下圖
5、Constant pool常量池
Constant pool: #1 = Methodref #5.#14 // java/lang/Object."<init>":()V #2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream; #3 = Methodref #17.#18 // java/io/PrintStream.println:(I)V #4 = Class #19 // Demo1 #5 = Class #20 // java/lang/Object #6 = Utf8 <init> #7 = Utf8 ()V #8 = Utf8 Code #9 = Utf8 LineNumberTable #10 = Utf8 main #11 = Utf8 ([Ljava/lang/String;)V #12 = Utf8 SourceFile #13 = Utf8 Demo1.java #14 = NameAndType #6:#7 // "<init>":()V #15 = Class #21 // java/lang/System #16 = NameAndType #22:#23 // out:Ljava/io/PrintStream; #17 = Class #24 // java/io/PrintStream #18 = NameAndType #25:#26 // println:(I)V #19 = Utf8 Demo1 #20 = Utf8 java/lang/Object #21 = Utf8 java/lang/System #22 = Utf8 out #23 = Utf8 Ljava/io/PrintStream; #24 = Utf8 java/io/PrintStream #25 = Utf8 println #26 = Utf8 (I)V
這裏面存的是類信息包含的靜態常量,編譯之後就能確認
常量名稱對應的含義如下圖
6、熟悉的構造函數
我並沒有寫構造函數,由此可見,沒有定義構造函數,會有隱式的無參構造函數
public Demo1(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0
7、程序入口main方法
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC //描述了方法的訪問控制 ACC_PUBLIC 公共的,ACC_STATIC 靜態的 Code: stack=3, locals=5, args_size=1//描述了locals本地變量的數量、args_size參數數量、stack方法對應棧幀中操作數棧的深度 0: sipush 500 3: istore_1 4: bipush 100 6: istore_2 7: iload_1 8: iload_2 9: idiv 10: istore_3 11: bipush 50 13: istore 4 15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 18: iload_3 19: iload 4 21: iadd 22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 25: return
flags:ACC_PUBLIC, ACC_STATIC
描述了方法的訪問控制
Code:stack=3, locals=5, args_size=1
描述了locals本地變量的數量(args、x、y、a、b)、args_size參數數量(args)、stack方法對應棧幀中操作數棧的深度
而後面的這些都是JVM執行弓|擎去執行這些源碼編譯過後的指令碼。javap翻譯 出來是操作符,class文件內存儲的是指令碼。前面的數字,是偏移量(字節),jvm根據這個去,區分不同的指令。詳情看資料包"JVM指令碼錶"
8、分析程序是怎麼完整運行的
a、第一步的話就就是編譯,編譯之後會把類信息加載到方法區
b、第二步的話就是要運行,就是JVM會創建線程來執行代碼,在虛擬機棧、程序計數器內存區域中創建線程獨佔的空間,這裏的話不涉及帶本地方法棧,因爲代碼都是Java代碼
c、第三步分析字節碼指令這裏配合查找JVM指令碼錶理解就行了
這裏要注意的是方法之間的調用指令是有綁定關係的,如下:
invokevirtual(運行時方法綁定調用方法)#3 指令剖析:
新方法調用,jvm會根據這個方法的描述,創建一個新的棧幀,方法的參數從操作數棧中彈出來(可以理解成把調用操作數棧的值當做變量給傳進了被調用的操作數棧中),然後程序技術器歸零重新開始計算,執行完println方法後又回到main方法棧幀中
d、最後執行return指令,void函數返回
五、總結
這一章,將JVM運行的核心邏輯進行了詳細剖析。
注: JVM運行原理中更底層實現,針對不同的操作系統或者處理器,會有不同的實現。
這也是JAVA能夠實現“一處編寫,處處運行”的原因。
我們開發人員理解到這個層次,就足夠學習掌握後面的多線程課程了
如果有問題,也可以留言給我