class文件的內容
class文件包含java程序運行的字節碼,是數據嚴格按照格式緊湊排列在
class文件中的二進制流,中間無任何分隔符;文件開頭是一個0xcafebabe的特殊標誌,象徵了咖啡寶貝(與Java圖標相對應),例如下圖:
這文件有複雜的格式,是專門給JVM看的,人類可以藉助工具閱讀。主要有版本、訪問標誌、常量池、當前類、超級類(父類)、接口、字段、方法、屬性等內容。
JVM學習
首先來看一下JVM運行時數據區的情況:
首先java源碼是被編譯器變異成class字節碼,然後可以看到JVM運行的時候是分爲線程共享部分和線程獨佔部分兩個區域的。下面一部分主要是JVM根據不同的操作系統進行的適配,主要是上面一部分需要掌握。
兩個概念
- 線程獨佔:每個線程都會有它獨立佔據的空間,隨線程生命週期而創建和銷燬
- 線程貢獻:所有線程都能訪問這塊內存數據,隨虛擬機或者GC創建和銷燬
由此我們可以知道方法區和堆內存中的資源是可以在不同線程彙總共享的,而棧內存和程序計數器中的資源是線程獨佔的。
線程共享區
方法區
方法區是用來存儲加載的類的信息、常量、靜態變量、編譯後的代碼等東西的。方法區在1.7以前把它叫做永久代,1.8之後叫做元數據空間。
堆內存
堆內存還可以細分爲老年代、新生代。主要就是用於存放對象的實例.。垃圾回收GC主要就是管理堆內存。如果滿了就會出現OutOfMemoryError,後續在內存模型中我會繼續詳細講解。
線程獨佔區
虛擬機棧
虛擬機棧主要就是存儲棧幀的,一個線程的執行會有一個或多個方法被執行,這個時候一個棧幀就對應一個方法。棧幀的內容包括:
- 局部變量表
- 操作數棧
- 動態鏈接
- 方法返回地址
- 附加信息等
棧內存默認大小爲1M,如果超出會報StackOverflowError;(就像你寫一個無限遞歸的方法,肯定會報這個錯)
本地方法棧
和虛擬機棧差不多,但是放的是本地方法(native修飾的),超出大小也會報StackOverflowError;
程序計數器
記錄當前線程執行字節碼的位置,存儲的是字節碼指令地址,如果執行Native方法,則計數器值爲空。它在每個線程的私有空間中佔有一部分空間,只是很小的一部分。它是因爲CPU在同一時間只會執行一個線程,所以需要記錄下當前執行的位置,使得激活的時候可以無縫銜接。
JVM實戰
光說不練假把式,要看JVM的具體運行原理,還需要藉助實例,來看一段代碼:
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);
}
}
先通過javac來編譯生成class字節碼:
javac Demo1.java
然後再通過javap命令來查看具體的內容:
javap -v Demo1.class>Demo1.txt #將生成的文件寫到Demo1.txt上
生成的文件:
Classfile /E:/wangyiyun/wangyiyun/src/Demo1.class
Last modified 2019-12-30; size 414 bytes
MD5 checksum ae6fa820973681b35609c75631cb255b
Compiled from "Demo1.java"
public class Demo1
minor version: 0 //次版本號
major version: 52 //主版本號 49對應5 50對應6 51->7 52->8
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"
基本信息
minor version: 0 //次版本號
major version: 52 //主版本號 49對應5 50對應6 51->7 52->8
flags: ACC_PUBLIC, ACC_SUPER //訪問標誌
上面三者對應的是版本信息和訪問標誌,能夠反映基本信息,下面是一張訪問標誌表:
Constant pool
常量池對應的是類編譯之後的靜態信息,編譯之後就能確認。
構造函數
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
因爲沒有定義構造函數,就默認使用了無參構造