3. Java內(nèi)存可見(jiàn)性
3.1 了解Java內(nèi)存模型
JVM內(nèi)存結(jié)構(gòu)、Java對(duì)象模型和Java內(nèi)存模型,這就是三個(gè)截然不同的概念,而這三個(gè)概念很容易混淆。這里詳細(xì)區(qū)別一下
3.1.1 JVM內(nèi)存結(jié)構(gòu)
我們都知道,Java代碼是要運(yùn)行在虛擬機(jī)上的,而虛擬機(jī)在執(zhí)行Java程序的過(guò)程中會(huì)把所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域,這些區(qū)域都有各自的用途。其中有些區(qū)域隨著虛擬機(jī)進(jìn)程的啟動(dòng)而存在,而有些區(qū)域則依賴用戶線程的啟動(dòng)和結(jié)束而建立和銷毀。
在《Java虛擬機(jī)規(guī)范(Java SE 8)》中描述了JVM運(yùn)行時(shí)內(nèi)存區(qū)域結(jié)構(gòu)如下:

JVM內(nèi)存結(jié)構(gòu),由Java虛擬機(jī)規(guī)范定義。描述的是Java程序執(zhí)行過(guò)程中,由JVM管理的不同數(shù)據(jù)區(qū)域。各個(gè)區(qū)域有其特定的功能。
3.1.2 Java對(duì)象模型
Java是一種面向?qū)ο蟮恼Z(yǔ)言,而Java對(duì)象在JVM中的存儲(chǔ)也是有一定的結(jié)構(gòu)的。而這個(gè)關(guān)于Java對(duì)象自身的存儲(chǔ)模型稱之為Java對(duì)象模型。
HotSpot虛擬機(jī)中(Sun JDK和OpenJDK中所帶的虛擬機(jī),也是目前使用范圍最廣的Java虛擬機(jī)),設(shè)計(jì)了一個(gè)OOP-Klass Model。OOP(Ordinary Object Pointer)指的是普通對(duì)象指針,而Klass用來(lái)描述對(duì)象實(shí)例的具體類型。
每一個(gè)Java類,在被JVM加載的時(shí)候,JVM會(huì)給這個(gè)類創(chuàng)建一個(gè)instanceKlass對(duì)象,保存在方法區(qū),用來(lái)在JVM層表示該Java類。當(dāng)我們?cè)贘ava代碼中,使用new創(chuàng)建一個(gè)對(duì)象的時(shí)候,JVM會(huì)創(chuàng)建一個(gè)instanceOopDesc對(duì)象,這個(gè)對(duì)象中包含了對(duì)象頭以及實(shí)例數(shù)據(jù)。

這就是一個(gè)簡(jiǎn)單的Java對(duì)象的OOP-Klass模型,即Java對(duì)象模型。
3.1.3 內(nèi)存模型
Java內(nèi)存模型就是一種符合內(nèi)存模型規(guī)范的,屏蔽了各種硬件和操作系統(tǒng)的訪問(wèn)差異的,保證了Java程序在各種平臺(tái)下對(duì)內(nèi)存的訪問(wèn)都能保證效果一致的機(jī)制及規(guī)范。
有興趣詳細(xì)了解Java內(nèi)存模型是什么,為什么要有Java內(nèi)存模型,Java內(nèi)存模型解決了什么問(wèn)題的學(xué)員,參考:https://www.hollischuang.com/archives/2550。
Java內(nèi)存模型是根據(jù)英文Java Memory Model(JMM)翻譯過(guò)來(lái)的。其實(shí)JMM并不像JVM內(nèi)存結(jié)構(gòu)一樣是真實(shí)存在的。他只是一個(gè)抽象的概念。JSR-133: Java Memory Model and Thread Specification中描述了,JMM是和多線程相關(guān)的,他描述了一組規(guī)則或規(guī)范,這個(gè)規(guī)范定義了一個(gè)線程對(duì)共享變量的寫入時(shí)對(duì)另一個(gè)線程是可見(jiàn)的。
簡(jiǎn)單總結(jié)下,Java的多線程之間是通過(guò)共享內(nèi)存進(jìn)行通信的,而由于采用共享內(nèi)存進(jìn)行通信,在通信過(guò)程中會(huì)存在一系列如可見(jiàn)性、原子性、順序性等問(wèn)題,而JMM就是圍繞著多線程通信以及與其相關(guān)的一系列特性而建立的模型。JMM定義了一些語(yǔ)法集,這些語(yǔ)法集映射到Java語(yǔ)言中就是volatile、synchronized等關(guān)鍵字。

JMM線程操作內(nèi)存的基本的規(guī)則:
第一條關(guān)于線程與主內(nèi)存:線程對(duì)共享變量的所有操作都必須在自己的工作內(nèi)存(本地內(nèi)存)中進(jìn)行,不能直接從主內(nèi)存中讀寫
第二條關(guān)于線程間本地內(nèi)存:不同線程之間無(wú)法直接訪問(wèn)其他線程本地內(nèi)存中的變量,線程間變量值的傳遞需要經(jīng)過(guò)主內(nèi)存來(lái)完成。
-
主內(nèi)存
主要存儲(chǔ)的是Java實(shí)例對(duì)象,所有線程創(chuàng)建的實(shí)例對(duì)象都存放在主內(nèi)存中,不管該實(shí)例對(duì)象是成員變量還是方法中的本地變量(也稱局部變量),當(dāng)然也包括了共享的類信息、常量、靜態(tài)變量。由于是共享數(shù)據(jù)區(qū)域,多條線程對(duì)同一個(gè)變量進(jìn)行訪問(wèn)可能會(huì)發(fā)現(xiàn)線程安全問(wèn)題。
-
本地內(nèi)存
主要存儲(chǔ)當(dāng)前方法的所有本地變量信息(本地內(nèi)存中存儲(chǔ)著主內(nèi)存中的變量副本拷貝),每個(gè)線程只能訪問(wèn)自己的本地內(nèi)存,即線程中的本地變量對(duì)其它線程是不可見(jiàn)的,就算是兩個(gè)線程執(zhí)行的是同一段代碼,它們也會(huì)各自在自己的工作內(nèi)存中創(chuàng)建屬于當(dāng)前線程的本地變量,當(dāng)然也包括了字節(jié)碼行號(hào)指示器、相關(guān)Native方法的信息。注意由于工作內(nèi)存是每個(gè)線程的私有數(shù)據(jù),線程間無(wú)法相互訪問(wèn)工作內(nèi)存,因此存儲(chǔ)在工作內(nèi)存的數(shù)據(jù)不存在線程安全問(wèn)題。