JVM內(nèi)存模型和Java線程內(nèi)存模型
參考內(nèi)容:京東架構(gòu)師100分鐘帶你重新認(rèn)識(shí)Java內(nèi)存模型!讓你面試無(wú)憂!

本文主要分成以下部分:
簡(jiǎn)單介紹JVM的內(nèi)存模型,并且簡(jiǎn)要分析Java的Minor gc過(guò)程
介紹Java 線程內(nèi)存模型,并且從匯編角度的層面了解volatile
以上的內(nèi)容參考自站內(nèi)視頻。

JVM內(nèi)存模型概述
如圖所示,JVM內(nèi)存模型分成堆、虛擬機(jī)、本地方法棧、方法區(qū)、程序計(jì)數(shù)器這幾個(gè)部分。

首先來(lái)看看虛擬機(jī)棧
虛擬機(jī)棧
程序在執(zhí)行過(guò)程中會(huì)產(chǎn)生線程,比如Main線程或者是其他的子線程。對(duì)于一個(gè)線程JVM就會(huì)給他分配一個(gè)虛擬機(jī)棧,同時(shí)每一個(gè)線程都有各自的程序計(jì)數(shù)器。在同一個(gè)線程中會(huì)調(diào)用方法,對(duì)于方法調(diào)用,JVM會(huì)給他分配【棧幀】。棧幀中包括四個(gè)方面:

局部變量表
動(dòng)態(tài)鏈接
操作數(shù)棧
方法出口
局部變量表和操作數(shù)棧:
局部變量表表示創(chuàng)建的的名稱,這些數(shù)字會(huì)被放到操作數(shù)棧中。比如上面圖中的代碼:
iconst_1表示將int類型的常量1放如操作數(shù)棧,然后i_store1表示將操作數(shù)棧中的1出棧,并且放到局部變量1,也就是a處。對(duì)于變量b的操作同理。在進(jìn)行變量c的計(jì)算過(guò)程時(shí),后續(xù)的邏輯會(huì)將局部變量1和2(a和b)中的結(jié)果放入操作數(shù)棧進(jìn)行計(jì)算,最后結(jié)果返回。
動(dòng)態(tài)鏈接:關(guān)聯(lián)符號(hào)引用和內(nèi)存地址
方法出口:和上下文切換有關(guān)
本地方法棧
本地方法棧和虛擬機(jī)棧類似,但是里面放的是本地方法的相關(guān)的變量
方法區(qū)
方法區(qū)中主要放常量,靜態(tài)變量和類信息
堆
new出來(lái)的對(duì)象。在虛擬機(jī)棧中,比如如下的代碼:
user句柄是放在常量表中的,但是new 出來(lái)的對(duì)象放在堆上。同理在方法區(qū)中如果有靜態(tài)變量是引用類型,new出來(lái)的對(duì)象也放在堆上。

堆的劃分
堆空間主要分成以下幾個(gè)部分:Eden,s0,s1,老年代。最開(kāi)始new出來(lái)的對(duì)象放在Eden區(qū),如果當(dāng)Eden區(qū)存放滿時(shí),字節(jié)碼執(zhí)行引擎會(huì)啟動(dòng)一個(gè)線程做minor gc。
minor gc中使用的算法被稱作可達(dá)性算法??蛇_(dá)性算法會(huì)從gc Root對(duì)象開(kāi)始。
gc Root
這部分的對(duì)象指的是靜態(tài)變量,本地方法棧引用的對(duì)象,虛擬機(jī)棧中的局部變量表引用的對(duì)象、方法區(qū)中靜態(tài)屬性引用的對(duì)象、常量引用的對(duì)象。
可達(dá)性算法
算法首先從gc Root出發(fā),從這些節(jié)點(diǎn)向下搜索,走過(guò)的過(guò)程就是引用鏈。如果一個(gè)對(duì)象沒(méi)有任何引用鏈和它相連,那么這個(gè)對(duì)象就是不可達(dá)的。對(duì)于不可達(dá)的對(duì)象存留在Eden區(qū),可達(dá)的對(duì)象會(huì)在s0和s1 survivor區(qū)轉(zhuǎn)移。如果這些對(duì)象的對(duì)象頭中的分代年齡字段大于15,將會(huì)被放在老年區(qū)。

Java線程模型
java線程模型分成兩個(gè)部分,一個(gè)是共享內(nèi)存(主內(nèi)存),一個(gè)是線程中的獨(dú)立的內(nèi)存。這部分的內(nèi)存線程之間不能相互訪問(wèn)。如果同時(shí)有兩個(gè)線程,從主內(nèi)存中復(fù)制了一個(gè)對(duì)象并且進(jìn)行修改,修改完成的結(jié)果不會(huì)立刻存回主內(nèi)存,這就是并發(fā)中常出現(xiàn)的寫錯(cuò)誤的問(wèn)題。

為了解決這個(gè)問(wèn)題,我們通常會(huì)在變量之間加上volatile關(guān)鍵字。
volatile關(guān)鍵字的實(shí)現(xiàn)
volatile主要是解決的線程之間的共享變量的可見(jiàn)性的問(wèn)題,它解決的思路是基于JVM定義的一系列硬件層面的原子操作:

舉兩個(gè)線程為例,在沒(méi)有加volatile之前:

左邊的線程只是對(duì)于一個(gè)變量執(zhí)行了一個(gè)讀取的過(guò)程,右邊的線程除了讀取還做了修改的操作??梢钥吹綇墓ぷ鲀?nèi)存到主內(nèi)存需要經(jīng)歷store操作,然后修改值是經(jīng)歷write操作。正是因?yàn)檫@些操作不是原子操作,才會(huì)出現(xiàn)錯(cuò)誤。
volatile關(guān)鍵字早期的實(shí)現(xiàn)方式是對(duì)于總線加鎖的機(jī)制,操作一個(gè)共享變量的時(shí)候,其他的線程都不能對(duì)緩存了這個(gè)共享變量的地址的緩存。但是這樣的開(kāi)銷比較大。
現(xiàn)在的volatile關(guān)鍵字是基于MESI協(xié)議的。volatile關(guān)鍵字在被修改時(shí),會(huì)立刻會(huì)寫到主內(nèi)存,同時(shí)開(kāi)啟MESI協(xié)議,CPU會(huì)開(kāi)啟總線嗅探機(jī)制,使得自身的變量失效。