首先看下一個Java程序是如何在機器上執行的:
Java源程序(.java文件)-->Java編譯器(如Eclipse)
-->字節碼(.class文件)
-->
JVM編譯器-->
裝配
-->機器碼
-->
經過系統總線-->
微處理器-->
邏輯門-->
電路-->
設備硬件.
JVM的內存模型
JVM內部的Java內存模型被分爲線程棧和堆兩塊。如下圖所示就是Java內存模型的邏輯圖:
JVM中每個線程都運行在它自己的線程棧中,線程棧中包含了當前線程正在執行的方法(所有的方法都在棧中被調用執行)。線程棧也包含了所有正在執行的方法的局部變量。一個線程創建的局部變量對其他線程都是不可見的。即使當兩個線程都在執行同一段代碼(局部變量),兩個線程都會在他們自己的線程棧中創建局部變量。因此,每一個線程都有它自己的方法局部變量控制權。注意,只有局部變量是每個線程互相獨立的,全局變量的生命週期是屬於對象的,所以全局變量是所有線程共享的。
堆內存包含了所有程序中創建的對象,不管是哪個線程創建都保存在堆內存,他們是所有線程共享的。不管對象是是否被傳遞給局部變量,還是作爲其它對象的變量所創建,對象都是存儲在堆上。總之,任何情況下,對象都保存在堆內存。如下圖就是線程棧的局部變量存儲和堆內存上對象的存儲邏輯圖:
簡單的局部變量類型(八大基本類型)是完全存儲在線程棧中的。
但是一個局部的引用型變量,這個引用本身是存儲在線程棧中,但是其引用的對象仍然在堆內存(因爲引用型變量其值就是一個對象地址,所有的線程都有一份此地址,均指向同一個對象)。
一個對象可以包含一些方法同時,方法裏可能包含有一些局部變量,這些局部變量都存儲在線程棧中,儘管對象的方法是被存儲在堆。一個對象的成員變量是隨着對象本身被存儲在堆內存上的,不管此成員變量是基本類型還是引用類型。
static變量也被存儲在堆內存。
堆內存上的所有對象都可以被任意的線程使用,當一個線程能夠使用一個對象時,它也可以使用對象的成員變量。如果兩個線程在同一時刻調用用同一個對象的方法,那麼這兩個線程都可以去使用這個對象的成員變量,但是每一個線程都有它自己的局部變量副本。
以下是邏輯圖:
兩個線程都有一系列的局部變量,其中有一個叫做Local Variable2的局部變量指向堆上共同的對象Object3.。兩個線程都分別有一個不同引用指向同一個對象,
這是什麼意思呢,意思就是Local variable2是一個引用類型變量,它是被保存在兩個線程各自的棧上面的,但是這兩個引用類型的引用(對象地址)都是指向了Object3
這個對象。
注意,對象Object3引用了Object2和Object4作爲其成員變量。通過Object3的成員引用變量,兩個線程就都可以訪問到對象Object2和Object4.。上面的圖也展示了一個
局部變量指向堆上兩個不同的對象,也就是兩個局部變量Local variable1一個指向了Object1,另一個指向了Object5.這種情況是如何產生的呢?理論上說,兩個線程都可以
使用Object1和Object5(因爲他們有相同的局部變量Local variable1),但是什麼情況下會出現同一個局部引用變量會指向不同的對象呢?這種情況的出現其實是很簡單的
一種操作,那就是在方法裏直接new了對象了,但是對於每一個線程來說其引用變量在每個線程棧上都有一個副本,但是每個指向的對象又是不一樣的!下面我們就用代碼來展示
上面的邏輯圖!
public class MyRunnable implements Runnable { public void run() { methodOne(); } public void methodOne() { int localVariable1 = 45; MySharedObject localVariable2 = MySharedObject.sharedInstance; // ... do more with local variables. methodTwo(); } public void methodTwo() { Integer localVariable1 = new Integer(99); } } public static class MySharedObject { // static variable pointing to instance of MySharedObject public static final MySharedObject sharedInstance = new MySharedObject(); // member variables pointing to two objects on the heap public Integer object2 = new Integer(22); public Integer object4 = new Integer(44); public long member1 = 12345; public long member2 = 67890; }
如果兩個線程都執行run()方法,那麼首先搶到CPU的線程將會執行,run()方法調用了methodOne(),methodOne()調用了methodTwo();
methoOne()聲明瞭一個基本類型的局部變量(localVariable1)和一個引用類型的局部變量localVariable2.
每一個線程在執行methodOne()的時候都會在它自己的線程棧中創建它localVariable1和localVariable2副本。變量localVariable1將會從每一個線程中完全的隔離開來,
它只存在線程的棧區空間(這裏僅僅是指這個localVariable1這個引用變量的值,而不是對象本身)。一個線程不能看到另一個線程的localVariable1發生了什麼變化。
每一個線程在執行methodOne()時也會創建他們的localVariable2副本,但是兩個不同localVariable2副本都是指向同一個堆內存上的對象
總結的來說:每個線程的線程棧上都存儲有局部變量的副本,對於基本類型的局部變量來說每個線程的的值都互不影響(因爲他m們各自的變量存儲的值就在其自己的棧中),
但是對於引用類型的局部變量來說,雖然各線程仍然有局部變量的引用,但是這些引用的值卻有可能指向同一個對象sharedInstance,由於此對象是static類型的,其時屬於整
個MySharedObject 類的,他在第一次被創建時第二次發現仍然存在並不會再次被創建,而是直接返回上次創建的那個sharedInstance。