網(wǎng)站開發(fā)全包免費(fèi)手機(jī)優(yōu)化大師下載安裝
文章目錄
- 概要
- 一、jdk7與jdk8內(nèi)存結(jié)構(gòu)的差異
- 二、程序計(jì)數(shù)器
- 三、虛擬機(jī)棧
- 3.1 什么是虛擬機(jī)棧
- 3.2 什么是棧幀
- 3.3 棧幀的組成
- 四、本地方法棧
- 五、堆
- 5.1 堆的特點(diǎn)
- 5.2 堆的結(jié)構(gòu)
- 5.3 堆的參數(shù)配置
- 六、方法區(qū)
- 6.1 方法區(qū)結(jié)構(gòu)
- 6.2 運(yùn)行時(shí)常量池
- 七、元空間
概要
根據(jù) JVM 規(guī)范,JVM 內(nèi)存共分為虛擬機(jī)棧、堆、方法區(qū)、程序計(jì)數(shù)器、本地方法棧五個(gè)部分。
其中各個(gè)部分的概述如下:
名稱 | 特征 | 作用 | 配置參數(shù) | 異常 |
---|---|---|---|---|
程序計(jì)數(shù)器 | 線程私有,生命周期與線程相同 | 字節(jié)碼行號指示器 | 無 | 無 |
虛擬機(jī)棧 | 線程私有,生命周期與線程相同,使用連續(xù)的內(nèi)存空間 | 存儲信息如上圖 | -Xss | StackOverflowError/ OutOfMemoryError |
堆 | 線程共享,生命周期與虛擬機(jī)相同,可以不使用連續(xù)的內(nèi)存地址 | 保存對象實(shí)例,所有對象實(shí)例(包括數(shù)組)都要在堆上分配 | -Xms -Xsx -Xmn | OutOfMemoryError |
方法區(qū) | 線程共享,生命周期與虛擬機(jī)相同, 可以不使用連續(xù)的內(nèi)存地址 | 存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù) | -XX:PermSize:16M -XX:MaxPermSize64M -XX:MetaspaceSize=16M -XX:MaxMetaspaceSize=64M | OutOfMemoryError |
本地方法棧 | 線程私有 | 為虛擬機(jī)使用到的Native 方法服務(wù) | 無 | StackOverflowError/ OutOfMemoryError |
JVM分為五大模塊: 類裝載器子系統(tǒng) 、 運(yùn)行時(shí)數(shù)據(jù)區(qū) 、 執(zhí)行引擎 、 本地方法接口 和 垃圾收集模塊 。
一、jdk7與jdk8內(nèi)存結(jié)構(gòu)的差異
Java7和Java8內(nèi)存結(jié)構(gòu)的不同主要體現(xiàn)在方法區(qū)的實(shí)現(xiàn)。
方法區(qū)是java虛擬機(jī)規(guī)范中定義的一種概念上的區(qū)域,不同的廠商可以對虛擬機(jī)進(jìn)行不同的實(shí)現(xiàn)。
我們通常使用的Java SE都是由Sun JDK和OpenJDK所提供,這也是應(yīng)用最廣泛的版本。而該版本使用的VM就是HotSpot VM。通常情況下,我們所講的java虛擬機(jī)指的就是HotSpot的版本。
JDK7的內(nèi)存結(jié)構(gòu)
永久代是 hotspot 在1.7及之前才有的設(shè)計(jì),1.8+,以及其他虛擬機(jī)并不存??梢哉f,永久代是1.7的 hotspot 偷懶的結(jié)果,他在堆里劃分了一塊來實(shí)現(xiàn)方法區(qū)的功能,叫永久代。
因?yàn)檫@樣可以借助堆的垃圾回收來管理方法區(qū)的內(nèi)存,而不用單獨(dú)為方法區(qū)再去編寫內(nèi)存管理程序。
同時(shí)代的其他虛擬機(jī),如 J9 , Jrockit 等,沒有這個(gè)概念。后來 hotspot認(rèn)識到,永久代來做這件事不是一個(gè)好主意。1.7已經(jīng)從永久代拿走了一部分?jǐn)?shù)據(jù)(靜態(tài)變量和運(yùn)行時(shí)常量池轉(zhuǎn)移到了堆中),直到1.8+徹底去掉了永久代,方法區(qū)大部分遷移到了 metaspace
(注意不是全部,不是全部)
JDK8的內(nèi)存結(jié)構(gòu)
從jdk1.8開始已經(jīng)將方法區(qū)中實(shí)現(xiàn)的永久代去掉了,并用元空間( class metadata space
)代替了之前的永久代,元空間的存儲位置是:本地內(nèi)存/直接內(nèi)存,并且將方法區(qū)大部分遷移到了元空間。
方法區(qū)Java8之后的變化小結(jié):
- 移除了永久代(
PermGen
),替換為元空間(Metaspace
) - 永久代中的
class metadata
(類元信息)轉(zhuǎn)移到了native memory(本地內(nèi)存,而不是虛擬機(jī)) - 永久代中的
interned Strings
(字符串常量池) 和class static variables
(類靜態(tài)變量)轉(zhuǎn)移到了Java heap
- 永久代參數(shù)(
PermSize MaxPermSize
)-> 元空間參數(shù)(MetaspaceSize MaxMetaspaceSize
)
Java8 為什么使用元空間替代永久代,這樣做有什么好處呢?
官方給出的解釋是:
- 移除永久代是為了融合 HotSpot JVM 與 JRockit VM 而做出的努力,因?yàn)?JRockit 沒有永久代,所以不需要配置永久代。
- 永久代內(nèi)存經(jīng)常不夠用或發(fā)生內(nèi)存溢出,拋出異常
java.lang.OutOfMemoryError: PermGen
。這是因?yàn)樵?JDK1.7 版本中,指定的PermGen
區(qū)大小為8M,由于PermGen
中類的元數(shù)據(jù)信息在每次FullGC
的時(shí)候都可能被收集,但回收率都偏低,成績很難令人滿意; - 為
PermGen
分配多大的空間很難確定,PermSize
的大小依賴于很多因素,比如,JVM 加載的 class 總數(shù)、常量池的大小和方法的大小等,而jdk1.8以后的元空間大小就只受本機(jī)總內(nèi)存的限制(如果不設(shè)置參數(shù)的話),因?yàn)樗褂玫氖潜镜貎?nèi)存。
二、程序計(jì)數(shù)器
程序計(jì)數(shù)器(Program Counter Register
):也叫PC寄存器,是一塊較小的內(nèi)存空間,它可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機(jī)的概念模型里,字節(jié)碼解釋器工作時(shí)就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令、分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來完成。
PC寄存器的特點(diǎn)
(1)區(qū)別于計(jì)算機(jī)硬件的pc寄存器,兩者不略有不同。計(jì)算機(jī)用pc寄存器來存放“偽指令”或地址,而相對于虛擬機(jī),pc寄存器它表現(xiàn)為一塊內(nèi)存,虛擬機(jī)的pc寄存器的功能也是存放偽指令,更確切的說存放的是將要執(zhí)行指令的地址。
(2)當(dāng)虛擬機(jī)正在執(zhí)行的方法是一個(gè)本地(native
)方法的時(shí)候,jvm的pc寄存器存儲的值是undefined
。
(3)程序計(jì)數(shù)器是線程私有的,它的生命周期與線程相同,每個(gè)線程都有一個(gè)。
(4)此內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError
情況的區(qū)域。
Java虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻,一個(gè)處
理器只會執(zhí)行一條線程中的指令。
因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程之間的計(jì)數(shù)
器互不影響,獨(dú)立存儲,我們稱這類內(nèi)存區(qū)域?yàn)椤熬€程私有”的內(nèi)存。
三、虛擬機(jī)棧
3.1 什么是虛擬機(jī)棧
Java虛擬機(jī)棧(Java Virtual Machine Stacks
)也是線程私有的,即生命周期和線程相同。Java虛擬機(jī)棧和線程同時(shí)創(chuàng)建,用于存儲棧幀。每個(gè)方法在執(zhí)行時(shí)都會創(chuàng)建一個(gè)棧幀(Stack Frame
),用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每一個(gè)方法從調(diào)用直到執(zhí)行完成的過程就對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程。
public class StackDemo {public static void main(String[] args) {StackDemo sd = new StackDemo();sd.A();}public void A() {int a = 10;System.out.println(" method A start");System.out.println(a);B();System.out.println("method A end");}public void B() {int b = 20;System.out.println(" method B start");C();System.out.println("method B end");}private void C() {int c = 30;System.out.println(" method C start");System.out.println("method C end");}
}
3.2 什么是棧幀
棧幀(Stack Frame)
是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)。棧幀存儲了方法的局部變量表、操作數(shù)棧、動態(tài)連接和方法返回地址等信息。每一個(gè)方法從調(diào)用至執(zhí)行完成的過程,都對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧里從入棧到出棧的過程。
3.3 棧幀的組成
棧幀大體都包含四個(gè)區(qū)域:局部變量表、操作數(shù)棧、動態(tài)連接、 返回地址
局部變量表
部變量表(Local Variable Table
)是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)定義的局部變量。
包括8種基本數(shù)據(jù)類型、對象引用(reference
類型)和returnAddress
類型(指向一條字節(jié)碼指令的地址)。其中64位長度的long
和double
類型的數(shù)據(jù)會占用2個(gè)局部變量空間(Slot
),其余的數(shù)據(jù)類型只占用1個(gè)。
操作數(shù)棧
操作數(shù)棧(Operand Stack
)也稱作操作棧,是一個(gè)后入先出棧(LIFO)。隨著方法執(zhí)行和字節(jié)碼指令的執(zhí)行,會從局部變量表或?qū)ο髮?shí)例的字段中復(fù)制常量或變量寫入到操作數(shù)棧,再隨著計(jì)算的進(jìn)行將棧中元素出棧到局部變量表或者返回給方法調(diào)用者,也就是出棧/入棧操作。
操作數(shù)棧作用小結(jié):
- 主要用于保存計(jì)算過程的中間結(jié)果,同時(shí)作為計(jì)算過程中變量臨時(shí)的存儲空間
- 操作數(shù)棧就是JVM執(zhí)行引擎的一個(gè)工作區(qū), Java虛擬機(jī)的解釋執(zhí)行引擎被稱為"基于棧的執(zhí)行引擎",其中所指的棧就是指-操作數(shù)棧
- 為了實(shí)現(xiàn)java的跨平臺,選擇了面向操作數(shù)棧的指令集架構(gòu)而沒有選擇直接基于CPU寄存器的指令集架構(gòu)(由執(zhí)行引擎面向更底層),基于棧的指令集主要的優(yōu)點(diǎn)就是可移植,寄存器由硬件直接提供,程序直接依賴這些硬件寄存器則不可避免地要受到硬件的約束,但是棧架構(gòu)指令集的主要缺點(diǎn)是執(zhí)行速度相對來說會稍慢一些,因?yàn)闂?shí)現(xiàn)在內(nèi)存之中,頻繁的棧訪問也就意味著頻繁的內(nèi)存訪問,相對于CPU來說,內(nèi)存始終是執(zhí)行速度的瓶頸;
動態(tài)鏈接
Java虛擬機(jī)棧中,每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧所屬方法的符號引用,持有這個(gè)引用的目的是為了支持方法調(diào)用過程中的動態(tài)鏈接(Dynamic Linking
)。
動態(tài)鏈接的作用是將符號引用轉(zhuǎn)換成直接引用
返回地址
方法返回地址存放調(diào)用該方法的PC寄存器的值。一個(gè)方法的結(jié)束,有兩種方式:正常地執(zhí)行完成,出現(xiàn)未處理的異常非正常的退出。無論通過哪種方式退出,在方法退出后都返回到該方法被調(diào)用的位置。方法正常退出時(shí),調(diào)用者的PC計(jì)數(shù)器的值作為返回地址,即調(diào)用該方法的指令的下一條指令的地址。而通過異常退出的,返回地址是要通過異常表來確定,棧幀中一般不會保存這部分信息。
無論方法是否正常完成,都需要返回到方法被調(diào)用的位置,程序才能繼續(xù)進(jìn)行。
四、本地方法棧
本地方法棧(Native Method Stacks
) 與虛擬機(jī)棧所發(fā)揮的作用是非常相似的, 其區(qū)別只是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼) 服務(wù), 而本地方法棧則是為虛擬機(jī)使用到的本地(Native
) 方法服務(wù)。
特點(diǎn):
- 本地方法棧加載
native
方法 - 是線程私有,生命周期跟線程相同,每個(gè)線程都有一個(gè)
- 跟java虛擬虛擬機(jī)棧一樣,規(guī)定了兩種類型異常:
a)StackOverFlowError
:線程請求的棧深度大于所允許的深度。
b)OutOfMemoryError
:本地方法棧擴(kuò)展時(shí)無法申請到足夠的內(nèi)存
五、堆
對于Java應(yīng)用程序來說, Java堆(Java Heap)
是虛擬機(jī)所管理的內(nèi)存中最大的一塊。 Java堆是被所 有線程共享的一塊內(nèi)存區(qū)域, 在虛擬機(jī)啟動時(shí)創(chuàng)建。 此內(nèi)存區(qū)域的唯一目的就是存放對象實(shí)例, Java 世界里“幾乎”所有的對象實(shí)例都在這里分配內(nèi)存。
5.1 堆的特點(diǎn)
- Java虛擬機(jī)所管理的內(nèi)存中最大的一塊
- jvm所有線程共享(堆中也包含私有的線程緩沖區(qū)
Thread Local Allocation Buffer (TLAB)
) - 在虛擬機(jī)啟動時(shí)創(chuàng)建
- 幾乎所有的對象實(shí)例以及數(shù)組都在這里分配內(nèi)存
- java堆時(shí)垃圾收集器管理的主要區(qū)域
- 從內(nèi)存回收的角度來看,由于現(xiàn)在收集器基本都采用分代收集算法,所以Java堆還可以細(xì)分為:新生代和老年代;新生代又可以分為:
Eden
空間、From Survivor
空間、To Survivor
空間。 - 堆是計(jì)算機(jī)物理存儲上不連續(xù)的、邏輯上是連續(xù)的,也是大小可調(diào)節(jié)的(通過
-Xms
和-Xmx
控制)。 - 方法結(jié)束后,堆中對象不會馬上移出僅僅在垃圾回收的時(shí)候時(shí)候才移除
- 如果在堆中沒有內(nèi)存完成實(shí)例的分配,并且堆也無法再擴(kuò)展時(shí),將會拋出
OutOfMemoryError
異常
5.2 堆的結(jié)構(gòu)
現(xiàn)在垃圾回收器都使用分代理論,堆空間也分類如下:
在Java7 Hotspot虛擬機(jī)中將Java堆內(nèi)存分為3個(gè)部分:
- 青年代
Young Generation
- 老年代
Old Generation
- 永久代
Permanent Generation
在Java8以后,由于方法區(qū)的內(nèi)存不在分配在Java堆上,而是存儲于本地內(nèi)存元空間Metaspace
中,所以永久代就不存在了
5.3 堆的參數(shù)配置
參考:參數(shù)配置
JVM中存儲java對象可以被分為兩類:
- 年輕代(
Young Gen
):年輕代主要存放新創(chuàng)建的對象,內(nèi)存大小相對會比較小,垃圾回收會比較頻繁。年輕代分成1個(gè)Eden Space
和2個(gè)Suvivor Space
(from
和to
)。 - 年老代(
Tenured Gen
):年老代主要存放JVM認(rèn)為生命周期比較長的對象(經(jīng)過幾次的Young Gen
的垃圾回收后仍然存在),內(nèi)存大小相對會比較大,垃圾回收也相對沒有那么頻繁.
-XX:NewRatio=ratio
Sets the ratio between young and old generation sizes. By default, this option is set to 2. The following example shows how to set the young/old ratio to 1:
-XX:NewRatio=1
-XX:NewRatio=2
:默認(rèn)值,標(biāo)識新生代占1,老年代占2,新生代占整個(gè)堆的1/3;
修改占比 -XX:NewPatio=4
, 標(biāo)識新生代占1 , 老年代占4 , 新生代占整個(gè)堆的1/5
XX:SurvivorRatio=ratio
Sets the ratio between eden space size and survivor space size. By default, this option is set to 8. The following example shows how to set the eden/survivor space ratio to 4:
-XX:SurvivorRatio=4
Eden
空間和另外兩個(gè)Survivor
空間占比分別為8:1:1
可以通過操作選項(xiàng) -XX:SurvivorRatio
調(diào)整這個(gè)空間比例。 比如 -XX:SurvivorRatio=8
堆的總大小可由-Xms
跟-Xmx
來配置
JVM 每次只會使用 Eden
和其中的一塊 Survivor
區(qū)域來為對象服務(wù),所以無論什么時(shí)候,總是有一塊 Survivor
區(qū)域是空閑著的。因此,新生代實(shí)際可用的內(nèi)存空間為 9/10
( 即90% )的新生代空間。
六、方法區(qū)
方法區(qū)(Method Area
) 與Java堆一樣, 是各個(gè)線程共享的內(nèi)存區(qū)域, 它用于存儲已被虛擬機(jī)加載的類型信息、常量、 靜態(tài)變量、 即時(shí)編譯器編譯后的代碼緩存等數(shù)據(jù)。
官方文檔:方法區(qū)
方法區(qū)中存儲的信息大致可分以下兩類:
- 類信息:主要指類相關(guān)的版本、字段、方法、接口描述、引用等
- 運(yùn)行時(shí)常量池:編譯階段生成的常量與符號引用、運(yùn)行時(shí)加入的動態(tài)變量
方法區(qū)在虛擬機(jī)規(guī)范里這是一個(gè)邏輯概念,元空間、永久代是方法區(qū)具體的落地實(shí)現(xiàn)。
在jdk1.6里,用永久代來實(shí)現(xiàn)方法區(qū),物理空間上用的時(shí)堆的內(nèi)存(目的是利用堆的垃圾回收來管理方
法區(qū)的內(nèi)存)。字符串常量是運(yùn)行時(shí)常量池的一部分,也就是歸屬于方法區(qū),放在了永久代里。這個(gè)時(shí)候經(jīng)常會出現(xiàn)的一個(gè)錯(cuò)誤就是:java.lang.OutOfMemoryError: PermGen space
。
jdk1.7已經(jīng)從永久代拿了一部分?jǐn)?shù)據(jù)(靜態(tài)變量和運(yùn)行時(shí)常量池)轉(zhuǎn)移到了堆中
6.1 方法區(qū)結(jié)構(gòu)
類加載器將Class文件加載到內(nèi)存之后,將類的信息存儲到方法區(qū)中
方法區(qū)中存儲的內(nèi)容:
- 類型信息(域信息、方法信息)
- 運(yùn)行時(shí)常量池
類型信息
對每個(gè)加載的類型(類Class
、接口 interface
、枚舉enum
、注解 annotation
),JVM必須在方法區(qū)中存儲以下類型信息:
- 這個(gè)類型的完整有效名稱(全名 = 包名.類名)
- 這個(gè)類型直接父類的完整有效名(對于
interface
或是java.lang.Object
,都沒有父類) - 這個(gè)類型的修飾符(
public
,abstract
,final
的某個(gè)子集) - 這個(gè)類型直接接口的一個(gè)有序列表
域信息
域信息,即為類的屬性,成員變量
JVM必須在方法區(qū)中保存類所有的成員變量相關(guān)信息及聲明順序。
域的相關(guān)信息包括:域名稱、域類型、域修飾符(pυblic
、private
、protected
、static
、final
、volatile
、transient
的某個(gè)子集)
方法信息
JVM必須保存所有方法的以下信息,同域信息一樣包括聲明順序:
- 方法名稱方法的返回類型(或
void
) - 方法參數(shù)的數(shù)量和類型(按順序)
- 方法的修飾符
public
、private
、protected
、static
、final
、synchronized
、native
、abstract
的一個(gè)子集 - 方法的字節(jié)碼bytecodes、操作數(shù)棧、局部變量表及大小(
abstract
和native
方法除外) - 異常表(
abstract
和native
方法除外)。每個(gè)異常處理的開始位置、結(jié)束位置、代碼處理在程序計(jì)數(shù)器中的偏
移地址、被捕獲的異常類的常量池索引
6.2 運(yùn)行時(shí)常量池
在jvm規(guī)范中,方法區(qū)除了存儲類信息之外,還包含了運(yùn)行時(shí)常量池。這里
首先要來講一下常量池的分類:
- Class常量池(靜態(tài)常量池)
- 運(yùn)行時(shí)常量池
- 字符串常量池(沒有明確的官方定義,其目的是為了更好地使用
String
)
常量池經(jīng)常會被搞混,要準(zhǔn)確地理解,首先來看基本定義
靜態(tài)常量池:存放編譯期間生成的各種字面量與符號引用
運(yùn)行時(shí)常量池:常量池表在運(yùn)行時(shí)的表現(xiàn)形式
編譯后的字節(jié)碼文件中包含了類型信息、域信息、方法信息等。通過ClassLoader
將字節(jié)碼文件的常量池(靜態(tài)常量池)中的信息加載到內(nèi)存中,存儲在了方法區(qū)的運(yùn)行時(shí)常量池中。
什么叫字面量與符號引用呢?
靜態(tài)常量池
.class
文件中除了有類的版本、字段、方法和接口等描述信息外,還有一項(xiàng)信息是常量池 ( Constant Pool Table
),用于存放編譯期間生成的各種字面量和符號引用,之所以說它是靜態(tài)的常量池是因?yàn)檫@些都只是躺在 .class
文件中的靜態(tài)數(shù)據(jù),此時(shí)還沒被加載到內(nèi)存.
/*** 1. 使用jdk1.8編譯* 2. 使用 javap -v ClassConstantPool.class*/
public class ClassConstantPool {private static String a = "abc";private String f = "def";private static int b = 123;private final int c = 456;private int d = 789;private float e;Gucci gucci = new Gucci();class Gucci {}
}
反編譯后,截取部分信息如下:
字面量:給基本類型變量的賦值就叫做字面量或字面值,字面量是編譯后生成的產(chǎn)物。
比如:String a = "b"
,這里的“b”就是字符串字面量,同樣類推還有整數(shù)字面量,浮點(diǎn)類型字面量,字符字面量
符號引用:符號引用以一組符號來描述所引用的目標(biāo)。符號引用可以是任何形式的字面量,JAVA 在編譯的時(shí)候一個(gè)每個(gè) java 類都會被編譯成一個(gè) class 文件,但在編譯的時(shí)候虛擬機(jī)并不知道所引用類的地址(實(shí)際地址),就用符號引用來代替,而在類的解析階段(類加載的一個(gè)過程)就是為了把這個(gè)符號引用轉(zhuǎn)化成為真正的地址。
比如:ClassConstantPool
類被編譯成一個(gè)class
文件時(shí),發(fā)現(xiàn)引用了Gucci
類,,但是在編譯時(shí)并不知道Gucci
類的實(shí)際內(nèi)存地址,因此只能使用符號引用(com/ocean/constance/ClassConstantPool$Gucci
)來代替。而在類裝載器裝
載Guuci
類時(shí),此時(shí)可以通過虛擬機(jī)獲取Guuci
類 的實(shí)際內(nèi)存地址,因此便可以將符號com/ocean/constance/ClassConstantPool$Gucci
替換為Guuci
類的實(shí)際內(nèi)存地址。
運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池( Runtime Constant Pool
)是每一個(gè)類或接口的常量池( Constant_Pool
)的運(yùn)行時(shí)表示形式,它包括了若干種不同的常量:從編譯期可知的數(shù)值字面量到必須運(yùn)行期解析后才能獲得的方法或字段引用。
運(yùn)行時(shí)常量池是在類加載完成之后,將 靜態(tài)常量池中的符號引用值轉(zhuǎn)存到運(yùn)行時(shí)常量池中,類在解析之后,將符號引用替換成直接引用。 另外運(yùn)行時(shí)常量池的物理存儲位置要注意兩點(diǎn):
- 運(yùn)行時(shí)常量池在 JDK1.7 版本之后,就移到堆內(nèi)存中了,這里指的是物理空間,而邏輯上還是屬于方法區(qū)(方法區(qū)是邏輯分區(qū))。
- 在 JDK1.8 中,使用元空間代替永久代來實(shí)現(xiàn)方法區(qū),但是方法區(qū)從定義上并沒有改變,所謂 “Your father will always be your father” 。變動的只是方法區(qū)中內(nèi)容的物理存放位置,運(yùn)行時(shí)常量池和字符串常量池被移動到了堆中而并沒有在元空間。但是不論它們物理上如何存放,邏輯上還是屬于方法區(qū)的。
字符串常量池
字符串常量池這個(gè)概念是有爭議的,很多正式的虛擬機(jī)規(guī)范文檔,都沒有對這個(gè)概念作一個(gè)明確的官方定義。
以 JDK1.8 為例,字符串常量池是存放在堆中,并且與 java.lang.String
類有很大關(guān)系。設(shè)計(jì)這塊內(nèi)存區(qū)域的原因在于: String
對象作為 Java 語言中重要的數(shù)據(jù)類型,是內(nèi)存中占據(jù)空間最大的一個(gè)對象。高效地使用字符串,可以提升系統(tǒng)的整體性能。
七、元空間
在JDK1.7之前,HotSpot 虛擬機(jī)用永久代來實(shí)現(xiàn)方法區(qū)。而從 JDK 1.8 開始,移除永久代,用元空間來實(shí)現(xiàn)方法區(qū),它位于本地內(nèi)存中,而不是虛擬機(jī)內(nèi)存中。
永久代跟元空間對比如下:
- 存儲位置不同:永久代在物理上是堆的一部分,和新生代、老年代的地址是連續(xù)的,而元空間屬于本地內(nèi)存。
- 存儲內(nèi)容不同:在原來的永久代劃分中,永久代用來存放類的元數(shù)據(jù)信息、靜態(tài)變量以及常量池等。現(xiàn)在類的元信
息存儲在元空間中,靜態(tài)變量和常量池等并入堆中,相當(dāng)于原來的永久代中的數(shù)據(jù),被元空間和堆內(nèi)存給瓜分了。
Metaspace相關(guān)參數(shù):
XX:MetaspaceSize
·,初始空間大小,達(dá)到該值就會觸發(fā)垃圾收集進(jìn)行類型卸載,同時(shí)GC會對該值進(jìn)行調(diào)整:如
果釋放了大量的空間,就適當(dāng)降低該值;如果釋放了很少的空間,那么在不超過MaxMetaspaceSize
時(shí),適當(dāng)提高該值。-XX:MaxMetaspaceSize
,最大空間,默認(rèn)是沒有限制的。如果沒有使用該參數(shù)來設(shè)置類的元數(shù)據(jù)的大小,其最大可利用空間是整個(gè)系統(tǒng)內(nèi)存的可用空間。JVM也可以增加本地內(nèi)存空間來滿足類元數(shù)據(jù)信息的存儲。但是如果沒有設(shè)置最大值,則可能存在bug導(dǎo)致Metaspace的空間在不停的擴(kuò)展,會導(dǎo)致機(jī)器的內(nèi)存不足;進(jìn)而可能出現(xiàn)swap內(nèi)存被耗盡;最終導(dǎo)致進(jìn)程直接被系統(tǒng)直接kill掉。如果設(shè)置了該參數(shù),當(dāng)Metaspace剩余空間不足,會拋出:java.lang.OutOfMemoryError: Metaspace space
-XX:MinMetaspaceFreeRatio
,在GC之后,最小的Metaspace剩余空間容量的百分比,減少為分配空間所導(dǎo)致的垃圾收集
-XX:MaxMetaspaceFreeRatio
,在GC之后,最大的Metaspace剩余空間容量的百分比,減少為釋放空間所導(dǎo)致的垃圾收集