運(yùn)行時(shí)數(shù)據(jù)區(qū)域
JAVA方法運(yùn)行的內(nèi)存區(qū)域
- 程序計(jì)數(shù)器
較小的內(nèi)存空間,當(dāng)前線程執(zhí)行的字節(jié)碼的行號指示器;各線程之間獨(dú)立存儲(chǔ)品嚣,互不影響。
程序計(jì)數(shù)器是一塊很小的內(nèi)存空間钧大,主要用來記錄各個(gè)線程執(zhí)行的字節(jié)碼的地址翰撑,例如,分支啊央、循環(huán)眶诈、跳轉(zhuǎn)、異常瓜饥、線程恢復(fù)等都依賴于計(jì)數(shù)器逝撬。
由于 Java 是多線程語言,當(dāng)執(zhí)行的線程數(shù)量超過 CPU 核數(shù)時(shí)乓土,線程之間會(huì)根據(jù)時(shí)間片輪詢爭奪 CPU 資源宪潮。如果一個(gè)線程的時(shí)間片用完了,或者是其它原因?qū)е逻@個(gè)線程的 CPU 資源被提前搶奪趣苏,那么這個(gè)退出的線程就需要單獨(dú)的一個(gè)程序計(jì)數(shù)器狡相,來記錄下一條運(yùn)行的指令。
程序計(jì)數(shù)器也是JVM中唯一不會(huì)OOM(OutOfMemory)的內(nèi)存區(qū)域 - 虛擬機(jī)棧
棧是什么樣的數(shù)據(jù)結(jié)構(gòu)食磕?先進(jìn)后出(FILO)的數(shù)據(jù)結(jié)構(gòu)尽棕,
虛擬機(jī)棧在JVM運(yùn)行過程中存儲(chǔ)當(dāng)前線程運(yùn)行方法所需的數(shù)據(jù),指令彬伦、返回地址滔悉。
Java 虛擬機(jī)棧是基于線程的伊诵。哪怕你只有一個(gè) main() 方法,也是以線程的方式運(yùn)行的回官。在線程的生命周期中曹宴,參與計(jì)算的數(shù)據(jù)會(huì)頻繁地入棧和出棧,棧的生命周期是和線程一樣的孙乖。
棧里的每條數(shù)據(jù)浙炼,就是棧幀份氧。在每個(gè) Java 方法被調(diào)用的時(shí)候唯袄,都會(huì)創(chuàng)建一個(gè)棧幀,并入棧蜗帜。一旦完成相應(yīng)的調(diào)用恋拷,則出棧。所有的棧幀都出棧后厅缺,線程也就結(jié)束了蔬顾。
每個(gè)棧幀,都包含四個(gè)區(qū)域:(局部變量表湘捎、操作數(shù)棧诀豁、動(dòng)態(tài)連接、返回地址)
棧的大小缺省為1M窥妇,可用參數(shù) –Xss調(diào)整大小舷胜,例如-Xss256k
- 局部變量表:顧名思義就是局部變量的表,用于存放我們的局部變量的活翩。首先它是一個(gè)32位的長度烹骨,主要存放我們的Java的八大基礎(chǔ)數(shù)據(jù)類型,一般32位就可以存放下材泄,如果是64位的就使用高低位占用兩個(gè)也可以存放下沮焕,如果是局部的一些對象,比如我們的Object對象拉宗,我們只需要存放它的一個(gè)引用地址即可峦树。
- 操作數(shù)據(jù)棧:存放我們方法執(zhí)行的操作數(shù)的,它就是一個(gè)棧旦事,先進(jìn)后出的棧結(jié)構(gòu)魁巩,操作數(shù)棧,就是用來操作的族檬,操作的的元素可以是任意的java數(shù)據(jù)類型歪赢,所以我們知道一個(gè)方法剛剛開始的時(shí)候,這個(gè)方法的操作數(shù)棧就是空的单料,操作數(shù)棧運(yùn)行方法就是JVM一直運(yùn)行入棧/出棧的操作
- 動(dòng)態(tài)連接:Java語言特性多態(tài)(需要類運(yùn)行時(shí)才能確定具體的方法)埋凯。
- 返回地址:正常返回(調(diào)用程序計(jì)數(shù)器中的地址作為返回)点楼、異常的話(通過異常處理器表<非棧幀中的>來確定)
棧幀執(zhí)行對內(nèi)存區(qū)域的影響
- 字節(jié)碼助記碼解釋地址:<u>https://cloud.tencent.com/developer/article/1333540</u>
在JVM中,基于解釋執(zhí)行的這種方式是基于棧的引擎白对,這個(gè)說的棧掠廓,就是操作數(shù)棧。
本地(native)方法運(yùn)行的內(nèi)存區(qū)域
本地方法棧
- 本地方法棧跟 Java 虛擬機(jī)棧的功能類似甩恼,Java 虛擬機(jī)棧用于管理 Java 函數(shù)的調(diào)用蟀瞧,而本地方法棧則用于管理本地方法的調(diào)用。但本地方法并不是用 Java 實(shí)現(xiàn)的条摸,而是由 C 語言實(shí)現(xiàn)的悦污。
本地方法棧是和虛擬機(jī)棧非常相似的一個(gè)區(qū)域,它服務(wù)的對象是 native 方法钉蒲。你甚至可以認(rèn)為虛擬機(jī)棧和本地方法棧是同一個(gè)區(qū)域切端。
虛擬機(jī)規(guī)范無強(qiáng)制規(guī)定,各版本虛擬機(jī)自由實(shí)現(xiàn) 顷啼,HotSpot直接把本地方法棧和虛擬機(jī)棧合二為一 踏枣。
線程共享的區(qū)域
方法區(qū)/永久代
很多開發(fā)者都習(xí)慣將方法區(qū)稱為“永久代”,其實(shí)這兩者并不是等價(jià)的钙蒙。
HotSpot 虛擬機(jī)使用永久代來實(shí)現(xiàn)方法區(qū)茵瀑,但在其它虛擬機(jī)中,例如躬厌,Oracle 的 JRockit马昨、IBM 的 J9 就不存在永久代一說。因此烤咧,方法區(qū)只是 JVM 中規(guī)范的一部分偏陪,可以說,在 HotSpot 虛擬機(jī)中煮嫌,設(shè)計(jì)人員使用了永久代來實(shí)現(xiàn)了 JVM 規(guī)范的方法區(qū)笛谦。
方法區(qū)主要是用來存放已被虛擬機(jī)加載的類相關(guān)信息,包括類信息昌阿、靜態(tài)變量饥脑、常量、運(yùn)行時(shí)常量池懦冰、字符串常量池灶轰。
JVM 在執(zhí)行某個(gè)類的時(shí)候,必須先加載刷钢。在加載類(加載笋颤、驗(yàn)證、準(zhǔn)備、解析伴澄、初始化)的時(shí)候赋除,JVM 會(huì)先加載 class 文件,而在 class 文件中除了有類的版本非凌、字段举农、方法和接口等描述信息外,還有一項(xiàng)信息是常量池 (Constant Pool Table)敞嗡,用于存放編譯期間生成的各種字面量和符號引用颁糟。
字面量包括字符串(String a=“b”)、基本類型的常量(final 修飾的變量)喉悴,符號引用則包括類和方法的全限定名(例如 String 這個(gè)類棱貌,它的全限定名就是 Java/lang/String)、字段的名稱和描述符以及方法的名稱和描述符粥惧。
而當(dāng)類加載到內(nèi)存中后键畴,JVM 就會(huì)將 class 文件常量池中的內(nèi)容存放到運(yùn)行時(shí)的常量池中;在解析階段突雪,JVM 會(huì)把符號引用替換為直接引用(對象的索引值)。
例如涡贱,類中的一個(gè)字符串常量在 class 文件中時(shí)咏删,存放在 class 文件常量池中的;在 JVM 加載完類之后问词,JVM 會(huì)將這個(gè)字符串常量放到運(yùn)行時(shí)常量池中督函,并在解析階段,指定該字符串對象的索引值激挪。運(yùn)行時(shí)常量池是全局共享的辰狡,多個(gè)類共用一個(gè)運(yùn)行時(shí)常量池,class 文件中常量池多個(gè)相同的字符串在運(yùn)行時(shí)常量池只會(huì)存在一份垄分。
方法區(qū)與堆空間類似宛篇,也是一個(gè)共享內(nèi)存區(qū),所以方法區(qū)是線程共享的薄湿。假如兩個(gè)線程都試圖訪問方法區(qū)中的同一個(gè)類信息叫倍,而這個(gè)類還沒有裝入 JVM,那么此時(shí)就只允許一個(gè)線程去加載它豺瘤,另一個(gè)線程必須等待吆倦。在 HotSpot 虛擬機(jī)、Java7 版本中已經(jīng)將永久代的靜態(tài)變量和運(yùn)行時(shí)常量池轉(zhuǎn)移到了堆中坐求,其余部分則存儲(chǔ)在 JVM 的非堆內(nèi)存中蚕泽,而 Java8 版本已經(jīng)將方法區(qū)中實(shí)現(xiàn)的永久代去掉了,并用元空間(class metadata)代替了之前的永久代桥嗤,并且元空間的存儲(chǔ)位置是本地
元空間大小參數(shù):
jdk1.7及以前(初始和最大值):-XX:PermSize须妻;-XX:MaxPermSize派任;
jdk1.8以后(初始和最大值):-XX:MetaspaceSize; -XX:MaxMetaspaceSize
jdk1.8以后大小就只受本機(jī)總內(nèi)存的限制(如果不設(shè)置參數(shù)的話)
JVM參數(shù)參考:<u>https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html</u>
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ù)讼油、常量池的大小和方法的大小等。
深入辨析堆和棧
堆
堆是 JVM 上最大的內(nèi)存區(qū)域呢簸,我們申請的幾乎所有的對象矮台,都是在這里存儲(chǔ)的。我們常說的垃圾回收根时,操作的對象就是堆瘦赫。
堆空間一般是程序啟動(dòng)時(shí),就申請了蛤迎,但是并不一定會(huì)全部使用确虱。
隨著對象的頻繁創(chuàng)建,堆空間占用的越來越多忘苛,就需要不定期的對不再使用的對象進(jìn)行回收蝉娜。這個(gè)在 Java 中,就叫作 GC(Garbage Collection)扎唾。
那一個(gè)對象創(chuàng)建的時(shí)候召川,到底是在堆上分配,還是在棧上分配呢胸遇?這和兩個(gè)方面有關(guān):對象的類型和在 Java 類中存在的位置荧呐。
Java 的對象可以分為基本數(shù)據(jù)類型和普通對象。
對于普通對象來說,JVM 會(huì)首先在堆上創(chuàng)建對象倍阐,然后在其他地方使用的其實(shí)是它的引用概疆。比如,把這個(gè)引用保存在虛擬機(jī)棧的局部變量表中峰搪。
對于基本數(shù)據(jù)類型來說(byte岔冀、short、int概耻、long使套、float、double鞠柄、char)侦高,有兩種情況。當(dāng)你在方法體內(nèi)聲明了基本數(shù)據(jù)類型的對象厌杜,它就會(huì)在棧上直接分配奉呛。其他情況藤滥,都是在堆上分配竭恬。堆大小參數(shù):
-Xms:堆的最小值;
-Xmx:堆的最大值境析;
-Xmn:新生代的大心琶取馁痴;
-XX:NewSize;新生代最小值肺孤;
-XX:MaxNewSize:新生代最大值;
例如- Xmx256m直接內(nèi)存
不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分济欢,也不是java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域赠堵;如果使用了NIO,這塊區(qū)域會(huì)被頻繁使用,在java堆內(nèi)可以用directByteBuffer對象直接引用并操作法褥;
這塊內(nèi)存不受java堆大小限制茫叭,但受本機(jī)總內(nèi)存的限制,可以通過-XX:MaxDirectMemorySize來設(shè)置(默認(rèn)與堆內(nèi)存最大值一樣)半等,所以也會(huì)出現(xiàn)OOM異常揍愁。
內(nèi)存溢出類型
-
棧溢出
參數(shù):-Xss1m, 具體默認(rèn)值需要查看官網(wǎng):https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BABHDABI
HotSpot版本中棧的大小是固定的杀饵,是不支持拓展的莽囤。
java.lang.StackOverflowError 一般的方法調(diào)用是很難出現(xiàn)的,如果出現(xiàn)了可能會(huì)是無限遞歸切距。
虛擬機(jī)棧帶給我們的啟示:方法的執(zhí)行因?yàn)橐虬蓷E朽缎,所以天生要比實(shí)現(xiàn)同樣功能的循環(huán)慢,所以樹的遍歷算法中:遞歸和非遞歸(循環(huán)來實(shí)現(xiàn))都有存在的意義。遞歸代碼簡潔话肖,非遞歸代碼復(fù)雜但是速度較快北秽。
OutOfMemoryError:不斷建立線程,JVM申請棧內(nèi)存最筒,機(jī)器沒有足夠的內(nèi)存贺氓。(一般演示不出,演示出來機(jī)器也死了) 堆溢出
申請內(nèi)存空間,超出最大堆內(nèi)存空間床蜘。
如果是內(nèi)存溢出辙培,則通過 調(diào)大 -Xms,-Xmx參數(shù)悄泥。
如果不是內(nèi)存泄漏虏冻,就是說內(nèi)存中的對象卻是都是必須存活的,那么久應(yīng)該檢查JVM的堆參數(shù)設(shè)置弹囚,與機(jī)器的內(nèi)存對比厨相,看是否還有可以調(diào)整的空間,再從代碼上檢查是否存在某些對象生命周期過長鸥鹉、持有狀態(tài)時(shí)間過長蛮穿、存儲(chǔ)結(jié)構(gòu)設(shè)計(jì)不合理等情況,盡量減少程序運(yùn)行時(shí)的內(nèi)存消耗毁渗。方法區(qū)溢出
1)運(yùn)行時(shí)常量池溢出
2)方法區(qū)中保存的Class對象沒有被及時(shí)回收掉或者Class信息占用的內(nèi)存超過了我們配置践磅。
注意Class要被回收,條件比較苛刻(僅僅是可以灸异,不代表必然府适,因?yàn)檫€有一些參數(shù)可以進(jìn)行控制):
1、 該類所有的實(shí)例都已經(jīng)被回收肺樟,也就是堆中不存在該類的任何實(shí)例檐春。
2、 加載該類的ClassLoader已經(jīng)被回收么伯。
3疟暖、 該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法田柔。
代碼示例:
cglib是一個(gè)強(qiáng)大的俐巴,高性能,高質(zhì)量的Code生成類庫硬爆,它可以在運(yùn)行期擴(kuò)展Java類與實(shí)現(xiàn)Java接口欣舵。
CGLIB包的底層是通過使用一個(gè)小而快的字節(jié)碼處理框架ASM,來轉(zhuǎn)換字節(jié)碼并生成新的類摆屯。除了CGLIB包邻遏,腳本語言例如Groovy和BeanShell糠亩,也是使用ASM來生成java的字節(jié)碼。當(dāng)然不鼓勵(lì)直接使用ASM准验,因?yàn)樗竽惚仨殞VM內(nèi)部結(jié)構(gòu)包括class文件的格式和指令集都很熟悉赎线。
- 本機(jī)直接內(nèi)存溢出
直接內(nèi)存的容量可以通過MaxDirectMemorySize來設(shè)置(默認(rèn)與堆內(nèi)存最大值一樣),所以也會(huì)出現(xiàn)OOM異常糊饱;
由直接內(nèi)存導(dǎo)致的內(nèi)存溢出垂寥,一個(gè)比較明顯的特征是在HeapDump文件中不會(huì)看見有什么明顯的異常情況,如果發(fā)生了OOM另锋,同時(shí)Dump文件很小滞项,可以考慮重點(diǎn)排查下直接內(nèi)存方面的原因。
虛擬機(jī)優(yōu)化技術(shù)
-
方法內(nèi)聯(lián)的優(yōu)化行為夭坪,就是把目標(biāo)方法的代碼原封不動(dòng)的“復(fù)制”到調(diào)用的方法中文判,避免真實(shí)的方法調(diào)用而已。
- 在一般的模型中室梅,兩個(gè)不同的棧幀的內(nèi)存區(qū)域是獨(dú)立的戏仓,但是大部分的JVM在實(shí)現(xiàn)中會(huì)進(jìn)行一些優(yōu)化,使得兩個(gè)棧幀出現(xiàn)一部分重疊亡鼠。(主要體現(xiàn)在方法中有參數(shù)傳遞的情況)赏殃,讓下面棧幀的操作數(shù)棧和上面棧幀的部分局部變量重疊在一起,這樣做不但節(jié)約了一部分空間间涵,更加重要的是在進(jìn)行方法調(diào)用時(shí)就可以直接公用一部分?jǐn)?shù)據(jù)仁热,無需進(jìn)行額外的參數(shù)復(fù)制傳遞了。
從底層深入理解運(yùn)行時(shí)數(shù)據(jù)區(qū)
開啟HSDB工具
Jdk1.8啟動(dòng)JHSDB的時(shí)候必須將sawindbg.dll復(fù)制到對應(yīng)目錄的jre下
C:\Program Files\Java\jdk1.8.0_101\lib
執(zhí)行 java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
當(dāng)我們通過 Java 運(yùn)行以上代碼時(shí)勾哩,JVM 的整個(gè)處理過程如下:
JVM 向操作系統(tǒng)申請內(nèi)存抗蠢,JVM 第一步就是通過配置參數(shù)或者默認(rèn)配置參數(shù)向操作系統(tǒng)申請內(nèi)存空間。
JVM 獲得內(nèi)存空間后思劳,會(huì)根據(jù)配置參數(shù)分配堆物蝙、棧以及方法區(qū)的內(nèi)存大小。
完成上一個(gè)步驟后敢艰, JVM 首先會(huì)執(zhí)行構(gòu)造器,編譯器會(huì)在.java 文件被編譯成.class 文件時(shí)册赛,收集所有類的初始化代碼钠导,包括靜態(tài)變量賦值語句、靜態(tài)代碼塊森瘪、靜態(tài)方法牡属,靜態(tài)變量和常量放入方法區(qū)
執(zhí)行方法。啟動(dòng) main 線程扼睬,執(zhí)行 main 方法逮栅,開始執(zhí)行第一行代碼悴势。此時(shí)堆內(nèi)存中會(huì)創(chuàng)建一個(gè) Teacher 對象,對象引用 student 就存放在棧中措伐。
執(zhí)行其他方法時(shí)特纤,具體的操作:棧幀執(zhí)行對內(nèi)存區(qū)域的影響。<u>棧幀執(zhí)行對內(nèi)存區(qū)域的影響</u>