本章節內容參考:《深刻理解Java虛擬機》java
運行時數據區:緩存
本次只介紹用於程序運行的線程私有的內存模型。 多線程
虛擬機棧(FILO):java方法執行的內存模型。this
棧幀(線程執行的一個方法的內存模型,每調用一個方法,壓入一個棧幀)線程
局部變量表:編譯器可知的8種基本類型、reference類型、returnAddress類型對象
操做數棧:一個用於計算的臨時數據存儲區(明顯,此棧是爲了存放要操做的數據用的)blog
動態連接:支持java多態索引
返回地址:方法結束的地方。return/Exception ip
本地方法棧:Native方法執行的內存模型。內存
程序計數器:這個計數器記錄的是正在執行的虛擬機字節碼指令的地址(若是線程正在執行的是一個java方法)。 字節碼解釋器工做時,就是經過改變這個計數器的值來選取須要執行的字節碼 指令(分支,循環,跳轉、異常處理、線程恢復)
線程中,方法A調用方法B。
線程的執行的過程:
一、線程開始,分配虛擬機棧大小(JVM參數 -Xss:大小,1.5+默認1M),
二、執行方法A時,建立一個棧幀A壓入虛擬機棧頂,根據程序計數器中的記錄的下一個要執行的字節碼指令的地址,找到並執行指令(將要操做的數據壓入操做數棧棧頂,將操做結果放入局部變量表中,詳細過程參照下面「合代碼演示」部分)。
三、中間調用方法B,則建立棧幀B,接着執行方法B的指令,直到方法B結束(遇到方法返回的字節碼指令或異常),B棧幀出棧,若是有返回數據,將返回數據壓入棧幀A的操做數棧頂,方法A接着執行。
四、方法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緩存中的值是失效,不能用了,要用請從主存中從新獲取。