JVM記憶體結構
一張簡單的圖粗略描述:

- 每個線程都隻能通路自己的線程棧。
- 每個線程都不能通路(看不見)其他線程的局部變量。
- 所有原生類型的局部變量都存儲線上程棧中,是以對其他線程是不可見的。
- 線程可以将一個原生變量值的副本傳給另一個線程,但不能共享原生局部變量本身。
- 堆記憶體中包含了 Java 代碼中建立的所有對象,不管是哪個線程建立的。 其中也涵蓋了包裝類型(例如 Byte,Integer,Long 等)。
- 不管是建立一個對象并将其指派給局部變量, 還是指派給另一個對象的成員變量, 建立的對象都會被儲存到堆記憶體中。
- 如果是原生資料類型的局部變量,那麼它的内容就全部保留線上程棧上。
- 如果是對象引用,則棧中的局部變量槽位中儲存着對象的引用位址,而實際的對象内容儲存在堆中。
- 對象的成員變量與對象本身一起存儲在堆上, 不管成員變量的類型是原生數值,還是對象引用。
- 類的靜态變量則和類定義一樣都儲存在堆中。
總結一下: 方法中使用的原生資料類型和對象引用位址在棧上存儲;對象、對象成員與類定義、靜态變量在堆上。堆記憶體又稱為“共享堆”,堆中的所有對象,可以被所有線程通路, 隻要他們能拿到對象的引用位址。如果一個線程可以通路某個對象時,也就可以通路該對象的成員變量。如果兩個線程同時調用某個對象的同一方法,則它們都可以通路到這個對象的成員變量,但每個線程的局部變量副本是獨立的。
JVM記憶體整體結構
- 每啟動一個線程,JVM 就會在棧空間棧配置設定對應的 線程棧, 比如 1MB 的空間(-Xss1m)。
- 線程棧也叫做 Java 方法棧。 如果使用了JNI 方法,則會配置設定一個單獨的本地方法棧(Native Stack)。
- 線程執行過程中,一般會有多個方法組成調用棧(Stack Trace), 比如 A 調用 B,B調用 C…每執行到一個方法,就會建立對應的 棧幀(Frame)。
JVM 棧記憶體結構
- 棧幀是一個邏輯上的概念,具體的大小在一個方法編寫完成後基本上就能确定。
- 比如傳回值需要有一個空間存放,每個局部變量都需要對應的位址空間,此外還有給指令使用的操作數棧,以及 class 指針(辨別這個棧幀對應的是哪個類的方法, 指向非堆裡面的 Class 對象)。
JVM 堆記憶體結構
- 堆記憶體是所有線程共用的記憶體空間,JVM 将Heap 記憶體分為年輕代(Young generation)和老年代(Old generation, 也叫 Tenured)兩部分。
- 年輕代還劃分為 3 個記憶體池,新生代(Edenspace)和存活區(Survivor space), 在大部分GC 算法中有 2 個存活區(S0, S1),在我們可以觀察到的任何時刻,S0 和 S1 總有一個是空的,但一般較小,也不浪費多少空間。
- Non-Heap 本質上還是 Heap,隻是一般不歸 GC管理,裡面劃分為 3 個記憶體池。
- Metaspace, 以前叫持久代(永久代, Permanentgeneration), Java8 換了個名字叫 Metaspace。
- CCS, Compressed Class Space, 存放 class 資訊的,和 Metaspace 有交叉。
- Code Cache存放 JIT 編譯器編譯後的本地機器代碼
什麼是 JMM?
Java記憶體模型(Java Memory Model,JMM)JMM主要是為了規定了線程和記憶體之間的一些關系。根據JMM的設計,系統存在一個主記憶體(Main Memory),Java中所有變量都儲存在主存中,對于所有線程都是共享的。每條線程都有自己的工作記憶體(Working Memory),工作記憶體中儲存的是主存中某些變量的拷貝,線程對所有變量的操作都是在工作記憶體中進行,線程之間無法互相直接通路,變量傳遞均需要通過主存完成。
jvm和jmm之間的關系
JMM中的主記憶體、工作記憶體與JVM中的Java堆、棧、方法區等并不是同一個層次的記憶體劃分,這兩者基本上是沒有關系的,如果兩者一定要勉強對應起來,那從變量、主記憶體、工作記憶體的定義來看,主記憶體主要對應于Java堆中的對象執行個體資料部分,而工作記憶體則對應于虛拟機棧中的部分區域。從更低層次上說,主記憶體就直接對應于實體硬體的記憶體,而為了擷取更好的運作速度,虛拟機(甚至是硬體系統本身的優化措施)可能會讓工作記憶體優先存儲于寄存器和高速緩存中,因為程式運作時主要通路讀寫的是工作記憶體。