深入運(yùn)行時(shí)數(shù)據(jù)區(qū)
計(jì)算機(jī)體系結(jié)構(gòu)
JVM的設(shè)計(jì)實(shí)際上遵循了遵循馮諾依曼計(jì)算機(jī)結(jié)構(gòu)
CPU與內(nèi)存交互圖:
硬件一致性協(xié)議:
MSI、MESI、MOSI、Synapse、Firely、DragonProtocol
摩爾定律
摩爾定律是由英特爾(Intel)創(chuàng)始人之一戈登·摩爾(Gordon Moore)提出來的。其內(nèi)容為:當(dāng)價(jià)格不變時(shí),集成電路上可容納的晶體管數(shù)目,約每隔18個(gè)月便會(huì)增加一倍,性能也將提升一倍。換言之,每一美元所能買到的電腦性能,將每隔18個(gè)月翻兩倍以上。這一定律揭示了信息技術(shù)進(jìn)步的速度。
為了使摩爾定律更為準(zhǔn)確,在摩爾定律發(fā)現(xiàn)后10年,1975年的時(shí)候,摩爾又做了一些修改。將翻番的時(shí)間從一年半調(diào)整為兩年。
常量池分類:
1.靜態(tài)常量池
靜態(tài)常量池是相對(duì)于運(yùn)行時(shí)常量池來說的,屬于描述class文件結(jié)構(gòu)的一部分
由字面量和符號(hào)引用組成,在類被加載后會(huì)將靜態(tài)常量池加載到內(nèi)存中也就是運(yùn)行時(shí)常量池
字面量 :文本,字符串以及Final修飾的內(nèi)容
符號(hào)引用 :類,接口,方法,字段等相關(guān)的描述信息。
2.運(yùn)行時(shí)常量池
當(dāng)靜態(tài)常量池被加載到內(nèi)存后就會(huì)變成運(yùn)行時(shí)常量池。
也就是真正的把文件的內(nèi)容落地到JVM內(nèi)存了
3.字符串常量池
**設(shè)計(jì)理念:**字符串作為最常用的數(shù)據(jù)類型,為減小內(nèi)存的開銷,專門為其開辟了一塊內(nèi)存區(qū)域(字符串常量池)用以存放。
JDK1.6及之前版本,字符串常量池是位于永久代(相當(dāng)于現(xiàn)在的方法區(qū))。
JDK1.7之后,字符串常量池位于Heap堆中
面試常問點(diǎn):(筆試居多)
下列三種操作最多產(chǎn)生哪些對(duì)象
1.直接賦值
String a ="aaaa";
解析:
最多創(chuàng)建一個(gè)字符串對(duì)象。
首先“aaaa”會(huì)被認(rèn)為字面量,先在字符串常量池中查找(.equals()),如果沒有找到,在堆中創(chuàng)建“aaaa”字符串對(duì)象,并且將“aaaa”的引用維護(hù)到字符串常量池中(實(shí)際是一個(gè)hashTable結(jié)構(gòu),存放key-value結(jié)構(gòu)數(shù)據(jù)),再返回該引用;如果在字符串常量池中已經(jīng)存在“aaaa”的引用,直接返回該引用。
2.new String()
String a =new String("aaaa");
解析:
最多會(huì)創(chuàng)建兩個(gè)對(duì)象。
首先“aaaa”會(huì)被認(rèn)為字面量,先在字符串常量池中查找(.equals()),如果沒有找到,在堆中創(chuàng)建“aaaa”字符串對(duì)象,然后再在堆中創(chuàng)建一個(gè)“aaaa”對(duì)象,返回后面“aaaa”的引用;
3.intern()
String s1 = new String("yzt");
String s2 = s1.intern();
System.out.println(s1 == s2); //false
解析:
String中的intern方法是一個(gè) native 的方法,當(dāng)調(diào)用 intern方法時(shí),如果常量池已經(jīng)包含一個(gè)等于此String對(duì)象的字符串(用equals(object)方法確定),則返回池中的字符串。否則,將intern返回的引用指向當(dāng)前字符串 s1(jdk1.6版本需要將s1 復(fù)制到字符串常量池里)
常量池在內(nèi)存中的布局:
運(yùn)行時(shí)數(shù)據(jù)區(qū)(Run-Time Data Areas)
在裝載階段的第(2),(3)步可以發(fā)現(xiàn)有運(yùn)行時(shí)數(shù)據(jù),堆,方法區(qū)等名詞 (2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu) (3)在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為對(duì)方法區(qū)中這些數(shù)據(jù)的訪問入口 說白了就是類文件被類裝載器裝載進(jìn)來之后,類中的內(nèi)容(比如變量,常量,方法,對(duì)象等這些數(shù)據(jù)得要有個(gè)去處,也就是要存儲(chǔ)起來,存儲(chǔ)的位置肯定是在JVM中有對(duì)應(yīng)的空間)
2.3.1 官網(wǎng)概括
官網(wǎng)
:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
The Java Virtual Machine defines various run-time data areas that are used during execution of a program. Some of these data areas are created on Java Virtual Machine start-up and are destroyed only when the Java Virtual Machine exits. Other data areas are per thread. Per-thread data areas are created when a thread is created and destroyed when the thread exits.
2.3.2 圖解
Each run-time constant pool is allocated from the Java Virtual Machine's method area (§2.5.4).s
2.3.3 初步認(rèn)識(shí)
2.3.3.1 Method Area(方法區(qū))
(1)方法區(qū)是各個(gè)線程共享的內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads.
The method area is created on virtual machine start-up.
(2)雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻又一個(gè)別名叫做Non-Heap(非堆),目的是與Java堆區(qū)分開來
Although the method area is logically part of the heap,......
(3)用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)
It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.
(4)當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí),將拋出OutOfMemoryError異常
If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.
此時(shí)回看裝載階段的第2步,將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
如果這時(shí)候把從Class文件到裝載的第(1)和(2)步合并起來理解的話,可以畫個(gè)圖
值得說明的
JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)是一種規(guī)范,真正的實(shí)現(xiàn) 在JDK 8中就是Metaspace,在JDK6或7中就是Perm Space
2.3.3.2 Heap(堆)
(1)Java堆是Java虛擬機(jī)所管理內(nèi)存中最大的一塊,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,被所有線程共享。
(2)Java對(duì)象實(shí)例以及數(shù)組都在堆上分配。
The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.
The heap is created on virtual machine start-up.
此時(shí)回看裝載階段的第3步,在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為對(duì)方法區(qū)中這些數(shù)據(jù)的訪問入口
此時(shí)裝載(1)(2)(3)的圖可以改動(dòng)一下
2.3.3.3 Java Virtual Machine Stacks(虛擬機(jī)棧)
經(jīng)過上面的分析,類加載機(jī)制的裝載過程已經(jīng)完成,后續(xù)的鏈接,初始化也會(huì)相應(yīng)的生效。
**假如目前的階段是初始化完成了,后續(xù)做啥呢?肯定是Use使用咯,不用的話這樣折騰來折騰去有什么意義?那怎樣才能被使用到?換句話說里面內(nèi)容怎樣才能被執(zhí)行?比如通過主函數(shù)main調(diào)用其他方法,這種方式實(shí)際上是main線程執(zhí)行之后調(diào)用的方法,即要想使用里面的各種內(nèi)容,得要以線程為單位,執(zhí)行相應(yīng)的方法才行。**那一個(gè)線程執(zhí)行的狀態(tài)如何維護(hù)?一個(gè)線程可以執(zhí)行多少個(gè)方法?這樣的關(guān)系怎么維護(hù)呢?
(1)虛擬機(jī)棧是一個(gè)線程執(zhí)行的區(qū)域,保存著一個(gè)線程中方法的調(diào)用狀態(tài)。換句話說,一個(gè)Java線程的運(yùn)行狀態(tài),由一個(gè)虛擬機(jī)棧來保存,所以虛擬機(jī)棧肯定是線程私有的,獨(dú)有的,隨著線程的創(chuàng)建而創(chuàng)建。
Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread.
(2)每一個(gè)被線程執(zhí)行的方法,為該棧中的棧幀,即每個(gè)方法對(duì)應(yīng)一個(gè)棧幀。
調(diào)用一個(gè)方法,就會(huì)向棧中壓入一個(gè)棧幀;一個(gè)方法調(diào)用完成,就會(huì)把該棧幀從棧中彈出。
A Java Virtual Machine stack stores frames (§2.6).
A new frame is created each time a method is invoked. A frame is destroyed when its method invocation completes.
- 圖解棧和棧幀
void a(){
b();
}
void b(){
c();
}
void c(){
}
- 棧幀
官網(wǎng)
:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6
棧幀:每個(gè)棧幀對(duì)應(yīng)一個(gè)被調(diào)用的方法,可以理解為一個(gè)方法的運(yùn)行空間。
每個(gè)棧幀中包括局部變量表(Local Variables)、操作數(shù)棧(Operand Stack)、指向運(yùn)行時(shí)常量池的引用(A reference to the run-time constant pool)、方法返回地址(Return Address)和附加信息。
局部變量表:方法中定義的局部變量以及方法的參數(shù)存放在這張表中
局部變量表中的變量不可直接使用,如需要使用的話,必須通過相關(guān)指令將其加載至操作數(shù)棧中作為操作數(shù)使用。
操作數(shù)棧:以壓棧和出棧的方式存儲(chǔ)操作數(shù)的
動(dòng)態(tài)鏈接:每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用是為了支持方法調(diào)用過程中的動(dòng)態(tài)連接(Dynamic Linking)。
方法返回地址:當(dāng)一個(gè)方法開始執(zhí)行后,只有兩種方式可以退出,一種是遇到方法返回的字節(jié)碼指令;一種是遇見異常,并且這個(gè)異常沒有在方法體內(nèi)得到處理。
- 結(jié)合字節(jié)碼指令理解棧幀
javap -c Person.class > Person.txt
Compiled from "Person.java"
class Person {
...
public static int calc(int, int);
Code:
0: iconst_3 //將int類型常量3壓入[操作數(shù)棧]
1: istore_0 //將int類型值存入[局部變量0]
2: iload_0 //從[局部變量0]中裝載int類型值入棧
3: iload_1 //從[局部變量1]中裝載int類型值入棧
4: iadd //將棧頂元素彈出棧,執(zhí)行int類型的加法,結(jié)果入棧
5: istore_2 //將棧頂int類型值保存到[局部變量2]中
6: iload_2 //從[局部變量2]中裝載int類型值入棧
7: ireturn //從方法中返回int類型的數(shù)據(jù)
...
}
思考
:index的值是0還是1
On class method invocation, any parameters are passed in consecutive local variables starting from local variable 0. On instance method invocation, local variable 0 is always used to pass a reference to the object on which the instance method is being invoked (this in the Java programming language). Any parameters are subsequently passed in consecutive local variables starting from local variable 1.
2.3.3.4 The pc Register(程序計(jì)數(shù)器)
我們都知道一個(gè)JVM進(jìn)程中有多個(gè)線程在執(zhí)行,而線程中的內(nèi)容是否能夠擁有執(zhí)行權(quán),是根據(jù)CPU調(diào)度來的。
假如線程A正在執(zhí)行到某個(gè)地方,突然失去了CPU的執(zhí)行權(quán),切換到線程B了,然后當(dāng)線程A再獲得CPU執(zhí)行權(quán)的時(shí)候,怎么能繼續(xù)執(zhí)行呢?這就是需要在線程中維護(hù)一個(gè)變量,記錄線程執(zhí)行到的位置。
如果線程正在執(zhí)行Java方法,則計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;
如果正在執(zhí)行的是Native方法,則這個(gè)計(jì)數(shù)器為空。
The Java Virtual Machine can support many threads of execution at once (JLS §17). Each Java Virtual Machine thread has its own pc (program counter) register. At any point, each Java Virtual Machine thread is executing the code of a single method, namely the current method (§2.6) for that thread. If that method is not native, the pc register contains the address of the Java Virtual Machine instruction currently being executed. If the method currently being executed by the thread is native, the value of the Java Virtual Machine's pc register is undefined. The Java Virtual Machine's pc register is wide enough to hold a returnAddress or a native pointer on the specific platform.
2.3.3.5 Native Method Stacks(本地方法棧)
如果當(dāng)前線程執(zhí)行的方法是Native類型的,這些方法就會(huì)在本地方法棧中執(zhí)行。
那如果在Java方法執(zhí)行的時(shí)候調(diào)用native的方法呢?
棧指向堆
如果在棧幀中有一個(gè)變量,類型為引用類型,比如Object obj=new Object(),這時(shí)候就是典型的棧中元
素指向堆中的對(duì)象。
2.3.4.2 方法區(qū)指向堆
方法區(qū)中會(huì)存放靜態(tài)變量,常量等數(shù)據(jù)。如果是下面這種情況,就是典型的方法區(qū)中元素指向堆中的對(duì)
象。
private static Object obj=new Object();
2.3.4.3 堆指向方法區(qū)
What?堆還能指向方法區(qū)?
注意,方法區(qū)中會(huì)包含類的信息,堆中會(huì)有對(duì)象,那怎么知道對(duì)象是哪個(gè)類創(chuàng)建的呢?
思考
:
一個(gè)對(duì)象怎么知道它是由哪個(gè)類創(chuàng)建出來的?怎么記錄?這就需要了解一個(gè)Java對(duì)象的具體信息咯。文章來源:http://www.zghlxwxcb.cn/news/detail-624328.html
下節(jié)課給大家詳細(xì)講解對(duì)象的內(nèi)存布局文章來源地址http://www.zghlxwxcb.cn/news/detail-624328.html
到了這里,關(guān)于二、JVM-深入運(yùn)行時(shí)數(shù)據(jù)區(qū)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!