本章節內容參考:《深入理解Java虛擬機》
運行時數據區:
本次只介紹用於程序運行的線程私有的內存模型。
虛擬機棧(FILO):java方法執行的內存模型。
棧幀(線程執行的一個方法的內存模型,每調用一個方法,壓入一個棧幀)
局部變量表:編譯器可知的8種基本類型、reference類型、returnAddress類型
操作數棧:一個用於計算的臨時數據存儲區(明顯,此棧是為了存放要操作的數據用的)
動態鏈接:支持java多態
返回地址:方法結束的地方。return/Exception
本地方法棧:Native方法執行的內存模型。
程序計數器:這個計數器記錄的是正在執行的虛擬機字節碼指令的地址(如果線程正在執行的是一個java方法)。 字節碼解釋器工作時,就是通過改變這個計數器的值來選取需要執行的字節碼 指令(分支,循環,跳轉、異常處理、線程恢復)
線程中,方法A調用方法B。
線程的執行的過程:
1、線程開始,分配虛擬機棧大小(JVM參數 -Xss:大小,1.5+默認1M),
2、執行方法A時,創建一個棧幀A壓入虛擬機棧頂,根據程序計數器中的記錄的下一個要執行的字節碼指令的地址,找到並執行指令(將要操作的數據壓入操作數棧棧頂,將操作結果放入局部變量表中,詳細過程參照下面“合代碼演示”部分)。
3、中間調用方法B,則創建棧幀B,接着執行方法B的指令,直到方法B結束(遇到方法返回的字節碼指令或異常),B棧幀出棧,如果有返回數據,將返回數據壓入棧幀A的操作數棧頂,方法A接着執行。
4、方法A執行結束,彈出棧幀A,虛擬機棧中再無棧幀,此線程結束。
為更形象的理解,結合代碼演示(操作數棧和局部變量表):
源碼:
25. public void add() {
26. int a = 3;
27. int b = 4;
28. int c = a + b;
29. }
javap結果:
public void add();
descriptor: ()V
flags: ACC_PUBLIC
Code:
//操作數棧最大深度2,局部變量4, 方法入參1(this + 真正的入參, 如果是方法名add(int a, int b),args_size = 1(this) + 2(a和b) = 3)
stack=2, locals=4, args_size=1
0: iconst_3 // 將int類型常量3壓入操作數棧頂
1: istore_1 // 將操作數棧頂的數據彈出,存入局部變量表索引1
2: iconst_4 // 將int類型常量4壓入操作數棧棧頂
3: istore_2 // 將操作數棧頂的數據彈出,存入局部變量表索引2
4: iload_1 // 將局部變量表索引為1的數據加入到操作數棧頂
5: iload_2 // 將局部變量表索引為2的數據加入到操作數棧頂
6: iadd // 將棧中2個數相加,將結果入棧頂
7: istore_3 // 將棧頂結果彈出,存入局部變量表索引3
8: return
LineNumberTable:
line 26: 0 //java文件代碼第26行對應開始指令0
line 27: 2 //java文件代碼第26行對應開始指令2
line 28: 4 //java文件代碼第26行對應開始指令4
line 29: 8 //java文件代碼第26行對開始應指令8
LocalVariableTable: // 局部變量表,4個局部變量,this、a、b、c
Start Length Slot Name Signature
0 9 0 this LCongoPengYuyan;
2 7 1 a I
4 5 2 b I
8 1 3 c I
一些思考:
我們都知道,操作數棧存放的數據是(int、long、float、double、reference、returnType)這些類型,
reference指像的對象是在堆里面,堆是共享的,既然是所有線程共享的,為啥在多線程中,數據會不一致呢,線程A將對象O的某個屬性改了,而線程B拿到O的屬性的值還是未改變的?java還推出了統一的JMM模型。
其實,CPU執行的時候,是要將內存中的數據,加載到CPU緩存(寄存器等),多線程的時候,數據首先在寄存器中修改,改完后重新刷入內存中。解決多線程數據可見性的問題,java提供了Volatile關鍵字,
它的實現原理,對象操作后面加入Lock匯編指令。A線程在修改操作后,強制刷入主存,然后通知執行B線程的CPU,你CPU緩存中的值是失效,不能用了,要用請從主存中重新獲取。