JDK1.7相對(duì)于JDK1.6,主要的變化就是將永久代中的字符串常量池移到堆內(nèi)存中,交由堆管理。我們知道堆是JVM內(nèi)存管理的主要區(qū)域,那么將字符串常量池放到堆內(nèi)存中更方便高效的對(duì)字符串常量進(jìn)行管理和垃圾回收。
而JDK1.8相對(duì)于JDK1.7來(lái)說(shuō),主要區(qū)別有兩點(diǎn),一是將虛擬機(jī)棧和本地方法棧合二為一了,二是移除永久代,增加了元數(shù)據(jù)區(qū),元數(shù)據(jù)區(qū)使用本地內(nèi)存,只受計(jì)算機(jī)內(nèi)存大小的限制。而永久代使用的還是堆內(nèi)存空間,受堆內(nèi)存大小的限制。
記錄正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址。為了線(xiàn)程切換后能恢復(fù)到正確的位置,每個(gè)線(xiàn)程都需要一個(gè)獨(dú)立的程序計(jì)數(shù)器,各個(gè)線(xiàn)程之間互不影響,獨(dú)立存儲(chǔ),這也就是所謂的“線(xiàn)程私有區(qū)域”。
描述方法執(zhí)行的內(nèi)存模型,每個(gè)方法在執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,每個(gè)棧幀存放的是局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等信息,方法被調(diào)用到執(zhí)行完成對(duì)應(yīng)的是一個(gè)棧幀從入棧到出棧的過(guò)程。是線(xiàn)程私有的。
為虛擬機(jī)使用到的Native方法服務(wù)。注在HotSpot虛擬機(jī)中直接就把本地方法棧和虛擬機(jī)棧合二為一了。
存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。在jdk1.8中已經(jīng)去除了永久代,改用只受計(jì)算機(jī)本地內(nèi)存大小限制的元空間來(lái)實(shí)現(xiàn)方法區(qū),元空間參數(shù)(-XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=1024M)。
運(yùn)行時(shí)常量池是方法區(qū)的一部分。Class文件中除了有類(lèi)的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用于存放編譯器生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在類(lèi)加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。
1. 由于PermGen內(nèi)存經(jīng)常會(huì)溢出,引發(fā)惱人的 java.lang.OutOfMemoryError: PermGen,因此 JVM 的開(kāi)發(fā)者希望這一塊內(nèi)存可以更靈活地被管理,不要再經(jīng)常出現(xiàn)這樣的 OOM。
2. 移除 PermGen 可以促進(jìn) HotSpot JVM 與 JRockit VM 的融合,因?yàn)?JRockit 沒(méi)有永久代。
i++:剛開(kāi)始學(xué)java時(shí)候,i++先使用i,然后再自加1,為什么是這樣呢?
底層步驟:
1.從局部變量表取出 i 并壓入操作數(shù)棧。(入棧)
2.然后對(duì)局部變量表中的i自增1,將操作棧棧頂值取出使用。(自加1,出棧)
3.最后使用操作數(shù)棧的棧頂值更新局部變量表,如此線(xiàn)程從操作棧讀到的是自增之前的值。(更新)
++i:剛開(kāi)始學(xué)java時(shí)候,++i先自加1然后再使用i,為什么是這樣呢?
----------------------------------------------------------
底層步驟:
1.先對(duì)局部變量表的 i 自增 1。(自加1)
2.然后取出并壓入操作數(shù)棧。(入棧)
3.再將操作棧棧頂值取出使用。(出棧)
4.最后使用棧頂值更新局部變量表,線(xiàn)程從操作棧讀到的是自增之后的值。(更新)
--------------------------------------------
之前之所以說(shuō) i++ 不是原子操作,即使使用 volatile 修飾也不是線(xiàn)程安全,就是因?yàn)榭赡?i 被從局部變量表取出,壓入操作數(shù)棧,操作數(shù)棧中自增,使用棧頂值更新局部變量表(寄存器更新寫(xiě)入內(nèi)存),其中分為 3 步,volatile 保證可見(jiàn)性,保證每次從局部變量表讀取的都是最新的值,但可能這 3 步可能被另一個(gè)線(xiàn)程的 3 步打斷,產(chǎn)生數(shù)據(jù)互相覆蓋問(wèn)題,從而導(dǎo)致 i 的值比預(yù)期的小。
如果線(xiàn)程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,則拋出StackOverflowError。
堆內(nèi)存存儲(chǔ)對(duì)象實(shí)例。我們只要不斷地創(chuàng)建對(duì)象。并保證gc roots到對(duì)象之間有可達(dá)路徑來(lái)避免垃圾回收機(jī)制清除這些對(duì)象。就會(huì)在對(duì)象數(shù)量到達(dá)最大。堆容量限制后,產(chǎn)生內(nèi)存溢出異常。
public class Cat {
public static void main(String[] args) {
List list = new ArrayList();
while (true) {
list.add(new Cat());
}
}
}
1.棧是線(xiàn)程私有的,棧的生命周期和線(xiàn)程一樣,每個(gè)方法在執(zhí)行的時(shí)候就會(huì)創(chuàng)建一個(gè)棧幀,它包含局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息,局部變量表又包括基本數(shù)據(jù)類(lèi)型和對(duì)象的引用;
2.當(dāng)線(xiàn)程請(qǐng)求的棧深度超過(guò)了虛擬機(jī)允許的最大深度時(shí),會(huì)拋出StackOverFlowError異常,方法遞歸調(diào)用肯可能會(huì)出現(xiàn)該問(wèn)題;
3.調(diào)整參數(shù)-xss去調(diào)整jvm棧的大小;
在Java虛擬機(jī)中,對(duì)象是在Java堆中分配內(nèi)存的,這是一個(gè)普遍的常識(shí)。但是,有一種特殊情況,那就是如果經(jīng)過(guò)逃逸分析(Escape Analysis)后發(fā)現(xiàn),一個(gè)對(duì)象并沒(méi)有逃逸出方法的話(huà),那么就可能被優(yōu)化成棧上分配。這樣就無(wú)需在堆上分配內(nèi)存,也無(wú)須進(jìn)行垃圾回收了。這也是最常見(jiàn)的堆外存儲(chǔ)技術(shù)。
1)當(dāng)一個(gè)對(duì)象在方法中被定義后,對(duì)象只在方法內(nèi)部使用,則認(rèn)為沒(méi)有發(fā)生逃逸。
2)當(dāng)一個(gè)對(duì)象在方法中被定義后,它被外部方法所引用,則認(rèn)為發(fā)生逃逸。例如作為調(diào)用參數(shù)傳遞到其他地方中。
運(yùn)行時(shí)數(shù)據(jù)區(qū) | 是否存在 | 是否存在 |
---|---|---|
程序計(jì)數(shù)器 | 否 | 否 |
虛擬機(jī)棧 | 是 | 否 |
本地方法棧 | 是 | 否 |
方法區(qū) | 是(OOM) | 是 |
堆 | 是 | 是 |