首先分析一下JDK
JVM :英文名稱(Java Virtual Machine)運(yùn)行 class 文件
JRE :英文名稱(Java Runtime Environment)包括:jvm 的標(biāo)準(zhǔn)實(shí)現(xiàn)和 Java 的一些基本類庫。


JDK :英文名稱(Java Development Kit)它集成了 jre 和一些好用的小工具。例如:javac,javap,jar , jsonconsle 等。

談一下高級(jí)語言的發(fā)展過程及JAVA到處運(yùn)行的核心。
演化過程:機(jī)器語言 (0和1 二進(jìn)制)->??匯編語言 (用一些特定符號(hào)表示機(jī)器語言的碼表)->??高級(jí)語言 (java)
計(jì)算機(jī)只認(rèn)識(shí) 0和1,每個(gè)系統(tǒng)對(duì)機(jī)器語言的解釋是不同的。(例如:window :010101(隨便寫的) -> 可能解釋成?? 1+1? ,Linux : 010101(隨便寫的)-> 可能解釋成 1-1)
解釋一下機(jī)器語言為什么不用我們更好理解的10進(jìn)制來表示。
因?yàn)槭褂?a target="_blank">電子管來表示十種狀態(tài)過于復(fù)雜,所以所有的電子計(jì)算機(jī)中只有兩種基本的狀態(tài),開和關(guān)。(與或非門)也就是說,電子管的兩種狀態(tài)決定了以電子管為基礎(chǔ)的電子計(jì)算機(jī)采用二進(jìn)制來表示數(shù)字和數(shù)據(jù)。
剛開始,人們用 0 和 1也可以編寫代碼,一個(gè)簡單的計(jì)算,這成本很大啊,要寫多少個(gè)二進(jìn)制,腦補(bǔ)一下。
我門穿越回去就面對(duì)這樣的情況也會(huì)想辦法改進(jìn)的,看一下前人是怎么想的。他們想把 + - * / 等符號(hào) 編寫一套碼表出來。只需要編寫固定碼表就能表示機(jī)器碼了。
比如”地球”,中文,英文,法文都有不同的單詞去解釋。這個(gè)編碼也一樣。(美國ASCII碼,?中國GB2312碼)
我們看一下java 中的匯編語言 class 文件

匯編器(理解:匯編語言和機(jī)器碼的映射關(guān)系表) 能識(shí)別匯編語言 ->機(jī)器語言指令 。
后來的出現(xiàn)的高級(jí)語言也是因?yàn)檫壿嬏珡?fù)雜,匯編語言也受不了,又偷懶,搞出一套高級(jí)語言。例如 : java c 等。
java到處運(yùn)行的核心正是 有不同系統(tǒng)的jvm 去將Java 字節(jié)碼換成系統(tǒng)識(shí)別的機(jī)器碼而已。
我們學(xué)習(xí)了解JVM對(duì)我們有什么用那?
內(nèi)存溢出:OutOfMemoryError? ?StackOverflowError? ?
什么情況會(huì)出現(xiàn)以上異常。我們就需要知道代碼中的變量? 對(duì)象 常量 類信息 等保存的位置,我們才能定位問題。
線程間變量的共享問題。什么情況會(huì)出現(xiàn)線程安全問題。我們了解一下,jvm中哪些是線程共享的,哪些是線程獨(dú)享的就清楚了。

jvm 內(nèi)存分布

jvm運(yùn)行時(shí)數(shù)據(jù)區(qū)

1.程序計(jì)數(shù)器(線程獨(dú)享)
程序計(jì)數(shù)器,它保存的是程序當(dāng)前執(zhí)行的指令的地址,用來指示 執(zhí)行哪條指令的。
由于在JVM中,多線程是通過線程輪流切換來獲得CPU執(zhí)行時(shí)間的,因此,在任一具體時(shí)刻,一個(gè)CPU的內(nèi)核只會(huì)執(zhí)行一條線程中的指令,因此,為了能夠使得每個(gè)線程都在線程切換后能夠恢復(fù)在切換之前的程序執(zhí)行位置,每個(gè)線程都需要有自己獨(dú)立的程序計(jì)數(shù)器,
在JVM規(guī)范中規(guī)定,如果線程執(zhí)行的是非native方法,則程序計(jì)數(shù)器中保存的是當(dāng)前需要執(zhí)行的指令的地址;
如果線程執(zhí)行的是native方法,則程序計(jì)數(shù)器中的值是undefined。
2.虛擬機(jī)棧(線程獨(dú)享)
每個(gè)方法在執(zhí)行時(shí)會(huì)在虛擬機(jī)棧中創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)連接、方法返回地址、附加信息等信息。每個(gè)方法從調(diào)用到執(zhí)行完成的過程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中的入棧(壓棧)到出棧(彈棧)的過程。
?虛擬機(jī)棧使用的內(nèi)存不需要保證是連續(xù)的
虛擬機(jī)棧,那每個(gè)線程的 Java 虛擬機(jī)棧容量可以在線程創(chuàng)建的時(shí)候就已經(jīng)確定。
如果線程請(qǐng)求分配的棧容量超過了 Java 虛擬機(jī)棧允許的最大容量,Java 虛擬機(jī)將會(huì)拋出?StackOverflowError?異常
?Java 虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展,并且在嘗試擴(kuò)展的時(shí)候無法申請(qǐng)到足夠的內(nèi)存,或者在創(chuàng)建新的線程時(shí)沒有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的虛擬機(jī)棧,那 Java 虛擬機(jī)將拋出一個(gè)?OutOfMemoryError?異常

2.1棧幀(Stack Frame)
2.1.1:局部變量:用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。虛擬機(jī)通過索引定位的方式使用局部變量表。Class 文件格式屬性表中 Code 屬性的 max_locals 已經(jīng)確定了棧的最大深度,最小存儲(chǔ)單元 變量槽(Variable Slot)
2.1.2:操作數(shù)棧:方法的執(zhí)行操作在操作數(shù)棧中完成。舉個(gè)列子說明,
int i = 1;
int j = 2;
k = i + j;?
上面 幾行說明一下,首先 將i 壓入棧中,再將j壓入棧中。將i,j 出棧 運(yùn)算后 如果是局部變量就設(shè)置到對(duì)應(yīng)局部變量的對(duì)應(yīng)索引的Slot中。
2.1.3動(dòng)態(tài)連接
在 Class 文件格式的常量池(存儲(chǔ)字面量和符號(hào)引用)中存有大量的符號(hào)引用(1.類的全限定名,2.字段名和屬性,3.方法名和屬性),字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號(hào)引用為參數(shù)。這些符號(hào)引用一部分會(huì)在類加載過程的解析階段的時(shí)候轉(zhuǎn)化為直接引用(指向目標(biāo)的指針、相對(duì)偏移量或者是一個(gè)能夠直接定位到目標(biāo)的句柄),這種轉(zhuǎn)化稱為靜態(tài)解析。
另外一部分將在每一次的運(yùn)行期期間轉(zhuǎn)化為直接引用,這部分稱為動(dòng)態(tài)連接。就是說,JVM不認(rèn)識(shí)當(dāng)前棧幀要執(zhí)行那個(gè)方法。需要一個(gè)路徑指定
2.1.4返回方法地址:虛擬機(jī)會(huì)使用針對(duì)每種返回類型的操作來返回,返回值將從操作數(shù)棧出棧并且入棧到調(diào)用方法的方法棧幀中,當(dāng)前棧幀出棧,被調(diào)用方法的棧幀變成當(dāng)前棧幀,程序計(jì)數(shù)器將重置為調(diào)用這個(gè)方法的指令的下一條指令。
3.本地方法棧(線程獨(dú)享)
本地方法棧則是為虛擬機(jī)使用到的 Native 方法服務(wù)。(C C++ 代碼)
當(dāng)線程調(diào)用 Java 方法時(shí),虛擬機(jī)會(huì)創(chuàng)建一個(gè)棧幀并壓入 Java 虛擬機(jī)棧。然而當(dāng)它調(diào)用的是 native 方法時(shí),虛擬機(jī)會(huì)保持 Java 虛擬機(jī)棧不變,也不會(huì)向 Java 虛擬機(jī)棧中壓入新的棧幀,虛擬機(jī)只是簡單地動(dòng)態(tài)連接并直接調(diào)用指定的 native 方法。
4.方法區(qū)(線程共享)
方法區(qū)在JVM中也是一個(gè)非常重要的區(qū)域,在方法區(qū)中,存儲(chǔ)了每個(gè)類的信息(包括類的名稱、方法信息、字段信息)、靜態(tài)變量、常量以及編譯器編譯后的代碼等。在Class文件中除了類的字段、方法、接口等描述信息外,在方法區(qū)中有一個(gè)非常重要的部分就是運(yùn)行時(shí)常量池,它是每一個(gè)類或接口的常量池的運(yùn)行時(shí)表示形式,在類和接口被加載到JVM后,對(duì)應(yīng)的運(yùn)行時(shí)常量池就被創(chuàng)建出來。當(dāng)然并非Class文件常量池中的內(nèi)容才能進(jìn)入運(yùn)行時(shí)常量池,在運(yùn)行期間也可將新的常量放入運(yùn)行時(shí)常量池中,比如String的intern方法。
在JVM規(guī)范中,沒有強(qiáng)制要求方法區(qū)必須實(shí)現(xiàn)垃圾回收。很多人習(xí)慣將方法區(qū)稱為“永久代”,是因?yàn)镠otSpot虛擬機(jī)以永久代來實(shí)現(xiàn)方法區(qū),從而JVM的垃圾收集器可以像管理堆區(qū)一樣管理這部分區(qū)域,從而不需要專門為這部分設(shè)計(jì)垃圾回收機(jī)制。
字符串常量池在JDK6的時(shí)候還是存放在方法區(qū)
JDK7后則將字符串常量池移到了Java堆中,
JDK8則是純粹取消了方法區(qū)這個(gè)概念,取而代之的是”元空間(Metaspace)
5.堆(線程共享)
所有對(duì)象實(shí)例及數(shù)組都要在堆上分配內(nèi)存,但隨著JIT編譯器的發(fā)展和逃逸分析技術(shù)的成熟,這個(gè)說法也不是那么絕對(duì),但是大多數(shù)情況都是這樣的。
虛擬機(jī)規(guī)范Java堆可以存在物理上不連續(xù)的內(nèi)存空間,就像磁盤空間只要邏輯是連續(xù)的即可。它的內(nèi)存大小可以設(shè)為固定大小,也可以擴(kuò)展。
堆也是GC垃圾回收的主要位置。堆內(nèi)存分為新生代 (Young) 和老年代 (Old) ,新生代 (Young) 又被劃分為三個(gè)區(qū)域:Eden、From Survivor、To Survivor。
逃逸分析、棧上分配、標(biāo)量替換等優(yōu)化技術(shù)導(dǎo)致并不是所有對(duì)象都會(huì)在堆上分配。這里不再擴(kuò)展
從內(nèi)存分配的角度看,線程共享的 Java 堆中可能劃分出多個(gè)線程私有的線程本地分配緩存區(qū)(Thread Local Allocation Buffer,TLAB)。

TLAB 在堆上開辟一小塊
什么是棧上分配
因此,JVM提供了一種叫做棧上分配的概念,針對(duì)那些作用域不會(huì)逃逸出方法的對(duì)象,在分配內(nèi)存時(shí)不在將對(duì)象分配在堆內(nèi)存中,而是將對(duì)象屬性打散后分配在棧(線程私有的,屬于棧內(nèi)存)上,這樣,隨著方法的調(diào)用結(jié)束,??臻g的回收就會(huì)隨著將棧上分配的打散后的對(duì)象回收掉,不再給gc增加額外的無用負(fù)擔(dān),從而提升應(yīng)用程序整體的性能
棧上分配如何開啟
棧上分配需要有一定的前提
開啟逃逸分析 (-XX:+DoEscapeAnalysis)
逃逸分析的作用就是分析對(duì)象的作用域是否會(huì)逃逸出方法之外,再server虛擬機(jī)模式下才可以開啟(jdk1.6默認(rèn)開啟)
開啟標(biāo)量替換 (-XX:+EliminateAllocations)
標(biāo)量替換的作用是允許將對(duì)象根據(jù)屬性打散后分配再棧上,默認(rèn)該配置為開啟
如何查看逃逸分析的篩選結(jié)果
可以通過配置 -XX:+PrintEscapeAnalysis 開啟打印逃逸分析篩選結(jié)果