JVM虛擬機(一)-內存管理與運行時數據區(qū)剖析

JVM 全稱 Java Virtual Machine 是Java語言實現與平臺的無關性的關鍵锻煌。我們所說的 JVM硼补,狹義上指的就 HotSpot(因為JVM有很多版本,但是使用最多的是HotSpot)。如非特殊說明礁芦,我們都以 HotSpot 為準正勒。一般的高級語言如果要在不同的平臺上運行得院,至少需要編譯成不同的目標代碼。而引入Java語言虛擬機后章贞,Java語言在不同平臺上運行時不需要重新編譯尿招。Java語言使用模式Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節(jié)碼),就可以在多種平臺上不加修改地運行就谜。
跨平臺:
JVM在執(zhí)行字節(jié)碼時怪蔑,把字節(jié)碼解釋成具體平臺上的機器指令執(zhí)行。相當于現實生活中的翻譯丧荐。


JVM作用.png

跨語言:
JVM只識別字節(jié)碼缆瓣,所以JVM其實跟語言是解耦的,也就是沒有直接關聯虹统,并不是它翻譯Java文件弓坞,而是識別class文件,這個一般稱之為字節(jié)碼车荔。還有像Groovy 渡冻、Kotlin、Jruby等等語言忧便,它們其實也是編譯成字節(jié)碼族吻,所以也可以在JVM上面跑,這個就是JVM的跨語言特征珠增。

JVM超歌、JRE、JDK的關系

JVM只是一個翻譯蒂教,把Class翻譯成機器識別的代碼巍举,但是需要注意,JVM 不會自己生成代碼,需要大家編寫代碼,同時需要很多依賴類庫瓶逃,這個時候就需要用到JRE。
JRE是什么定枷,它除了包含JVM之外,提供了很多的類庫(就是我們說的jar包届氢,它可以提供一些即插即用的功能欠窒,比如讀取或者操作文件,連接網絡退子,使用I/O等等之類的)這些東西就是JRE提供的基礎類庫岖妄。JVM 標準加上實現的一大堆基礎類庫,就組成了 Java 的運行時環(huán)境寂祥,也就是我們常說的 JRE(Java Runtime Environment)荐虐。
但對于程序員來說,JRE還不夠丸凭。我寫完要編譯代碼福扬,還需要調試代碼腕铸,還需要打包代碼、有時候還需要反編譯代碼铛碑。所以我們會使用JDK狠裹,因為JDK還提供了一些非常好用的小工具,比如 javac(編譯代碼)汽烦、java涛菠、jar (打包代碼)、javap(反編譯<反匯編>)等撇吞。這個就是JDK俗冻。


Java SE體系架構.png

Java程序的運行過程

Java程序的運行過程是javac工具先將.Java文件編譯成.class文件,然后執(zhí)行的過程中通過Java類加載器ClassLoader將class文件加載到JVM 運行時數據區(qū) 牍颈,通過解釋執(zhí)行和JIT等執(zhí)行引擎調用操作系統(tǒng)的接口迄薄。如圖所示:

Java程序的運行過程.png

運行時數據區(qū)域

了解Java虛擬機主要是理解其運行時數據區(qū)原理。Java 引以為豪的就是它的自動內存管理機制煮岁。相比于 C++的手動內存管理讥蔽、復雜難以理解的指針等,Java 程序寫起來就方便的多人乓。在 Java 中勤篮,JVM 內存主要分為堆都毒、程序計數器色罚、方法區(qū)、虛擬機棧和本地方法棧账劲。運行時數據區(qū)結構如圖所示


運行時數據區(qū).png
Java數據存儲結構圖

線程獨享的區(qū)域

程序計數器

小的內存空間戳护,當前線程執(zhí)行的字節(jié)碼的行號指示器;各線程之間獨立存儲瀑焦,互不影響腌且。程序計數器是一塊很小的內存空間,主要用來記錄各個線程執(zhí)行的字節(jié)碼的地址榛瓮,例如铺董,分支、循環(huán)禀晓、跳轉精续、異常、線程恢復等都依賴于計數器粹懒。由于 Java 是多線程語言重付,當執(zhí)行的線程數量超過 CPU 核數時,線程之間會根據時間片輪詢爭奪 CPU 資源凫乖。如果一個線程的時間片用完了确垫,或者是其它原因導致這個線程的 CPU 資源被提前搶奪弓颈,那么這個退出的線程就需要單獨的一個程序計數器,來記錄下一條運行的指令删掀。
程序計數器也是JVM中唯一不會OOM(OutOfMemory)的內存區(qū)域

虛擬機棧

虛擬機棧在JVM運行過程中存儲當前線程運行方法所需的數據翔冀,指令、返回地址爬迟。Java 虛擬機棧是基于線程的橘蜜。哪怕你只有一個 main() 方法,也是以線程的方式運行的付呕。在線程的生命周期中计福,參與計算的數據會頻繁地入棧和出棧,棧的生命周期是和線程一樣的徽职。棧里的每條數據象颖,就是棧幀。在每個 Java 方法被調用的時候姆钉,都會創(chuàng)建一個棧幀说订,并入棧。一旦完成相應的調用潮瓶,則出棧陶冷。所有的棧幀都出棧后,線程也就結束了毯辅。

棧幀

棧幀埂伦,虛擬機棧的存儲單元,虛擬機棧中存放著一個或多個棧幀思恐。每個棧幀沾谜,都包含四個區(qū)域:(局部變量表、操作數棧胀莹、動態(tài)連接基跑、返回地址)。棧的大小缺省為1M描焰,可用參數 –Xss調整大小媳否,例如-Xss256k

局部變量表

用于存放我們的局部變量的。首先它是一個32位的長度荆秦,主要存放我們的Java的八大基礎數據類型篱竭,一般32位就可以存放下,如果是64位的就使用高低位占用兩個也可以存放下萄凤,如果是局部的一些對象室抽,比如我們的Object對象,我們只需要存放它的一個引用地址即可靡努。

操作數棧

它就是一個棧坪圾,先進后出的棧結構晓折,操作數棧,就是用來操作的兽泄,操作的的元素可以是任意的java數據類型漓概,所以我們知道一個方法剛剛開始的時候,這個方法的操作數棧就是空的病梢,操作數棧運行方法就是JVM一直運行入棧/出棧的操作胃珍。在JVM中,基于解釋執(zhí)行的這種方式是基于棧的引擎蜓陌,這個說的棧觅彰,就是操作數棧。

動態(tài)連接

Java語言特性多態(tài)(需要類運行時才能確定具體的方法)钮热。

返回地址

正常返回(調用程序計數器中的地址作為返回)填抬、異常的話(通過異常處理器表<非棧幀中的>來確定)

一個簡單的work方法對應于在運行時數據區(qū)的字節(jié)碼
Java代碼對應的字節(jié)碼.png

字節(jié)碼的描述

想了解更多字節(jié)碼,可以通過這個網站學習字節(jié)碼
https://cloud.tencent.com/developer/article/1333540

本地方法棧

本地方法棧跟 Java 虛擬機棧的功能類似隧期,Java 虛擬機棧用于管理 Java 函數的調用飒责,而本地方法棧則用于管理本地方法的調用。但本地方法并不是用 Java 實現的仆潮,而是由 C 語言實現的宏蛉。本地方法棧是和虛擬機棧非常相似的一個區(qū)域,它服務的對象是 native 方法性置。你甚至可以認為虛擬機棧和本地方法棧是同一個區(qū)域拾并。虛擬機規(guī)范無強制規(guī)定,各版本虛擬機自由實現 蚌讼,HotSpot直接把本地方法棧和虛擬機棧合二為一

線程共享的區(qū)域

方法區(qū)/永久代

很多開發(fā)者都習慣將方法區(qū)稱為“永久代”辟灰,其實這兩者并不是等價的个榕。HotSpot 虛擬機使用永久代來實現方法區(qū)篡石,但在其它虛擬機中,例如西采,Oracle 的 JRockit凰萨、IBM 的 J9 就不存在永久代一說。因此械馆,方法區(qū)只是 JVM 中規(guī)范的一部分胖眷,可以說,在 HotSpot 虛擬機中霹崎,設計人員使用了永久代來實現了 JVM 規(guī)范的方法區(qū)珊搀。
方法區(qū)主要是用來存放已被虛擬機加載的類相關信息,包括類信息尾菇、靜態(tài)變量境析、常量囚枪、運行時常量池、字符串常量池劳淆。
JVM 在執(zhí)行某個類的時候链沼,必須先加載。在加載類(加載沛鸵、驗證括勺、準備、解析曲掰、初始化)的時候疾捍,JVM 會先加載 class 文件,而在 class 文件中除了有類的版本栏妖、字段拾氓、方法和接口等描述信息外,還有一項信息是常量池 (Constant Pool Table)底哥,用于存放編譯期間生成的各種字面量和符號引用咙鞍。
字面量包括字符串(String a=“b”)、基本類型的常量(final 修飾的變量)趾徽,符號引用則包括類和方法的全限定名(例如 String 這個類续滋,它的全限定名就是 Java/lang/String)、字段的名稱和描述符以及方法的名稱和描述符孵奶。而當類加載到內存中后疲酌,JVM 就會將 class 文件常量池中的內容存放到運行時的常量池中;在解析階段JVM 會把符號引用替換為直接引用(對象的索引值)了袁。例如朗恳,類中的一個字符串常量在 class 文件中時,存放在 class 文件常量池中的载绿;在 JVM 加載完類之后粥诫,JVM 會將這個字符串常量放到運行時常量池中,并在解析階段崭庸,指定該字符串對象的索引值怀浆。運行時常量池是全局共享的,多個類共用一個運行時常量池怕享,class 文件中常量池多個相同的字符串在運行時常量池只會存在一份执赡。
方法區(qū)與堆空間類似,也是一個共享內存區(qū)函筋,所以方法區(qū)是線程共享的沙合。假如兩個線程都試圖訪問方法區(qū)中的同一個類信息,而這個類還沒有裝入 JVM跌帐,那么此時就只允許一個線程去加載它首懈,另一個線程必須等待芳来。在 HotSpot 虛擬機、Java7 版本中已經將永久代的靜態(tài)變量和運行時常量池轉移到了堆中猜拾,其余部分則存儲在 JVM 的非堆內存中即舌,而 Java8 版本已經將方法區(qū)中實現的永久代去掉了,并用元空間(class metadata)代替了之前的永久代挎袜,并且元空間的存儲位置是本地顽聂。

元空間大小參數:

jdk1.7及以前(初始和最大值):-XX:PermSize;-XX:MaxPermSize盯仪;
jdk1.8以后(初始和最大值):-XX:MetaspaceSize紊搪; -XX:MaxMetaspaceSize
jdk1.8以后大小就只受本機總內存的限制(如果不設置參數的話)

Java8 為什么使用元空間替代永久代,這樣做有什么好處呢全景?

官方給出的解釋是:移除永久代是為了融合 HotSpot JVM 與 JRockit VM 而做出的努力耀石,因為 JRockit 沒有永久代,所以不需要配置永久代爸黄。永久代內存經常不夠用或發(fā)生內存溢出滞伟,拋出異常 java.lang.OutOfMemoryError: PermGen。這是因為在 JDK1.7 版本中炕贵,指定的 PermGen 區(qū)大小為 8M梆奈,由于 PermGen 中類的元數據信息在每次 FullGC 的時候都可能被收集,回收率都偏低称开,成績很難令人滿意亩钟;還有,為 PermGen 分配多大的空間很難確定鳖轰,PermSize 的大小依賴于很多因素清酥,比如,JVM 加載的 class 總數蕴侣、常量池的大小和方法的大小等焰轻。

堆是 JVM 上最大的內存區(qū)域,我們申請的幾乎所有的對象睛蛛,都是在這里存儲的鹦马。我們常說的垃圾回收胧谈,操作的對象就是堆忆肾。堆空間一般是程序啟動時,就申請了菱肖,但是并不一定會全部使用客冈。
隨著對象的頻繁創(chuàng)建,堆空間占用的越來越多稳强,就需要不定期的對不再使用的對象進行回收场仲。這個在 Java 中和悦,就叫作 GC(Garbage Collection)。那一個對象創(chuàng)建的時候渠缕,到底是在堆上分配鸽素,還是在棧上分配呢?這和兩個方面有關:對象的類型和在 Java 類中存在的位置亦鳞。
在常見的虛擬機中馍忽,將堆分為Eden區(qū),From區(qū)燕差,To區(qū)和老年代四塊區(qū)域遭笋。
Eden區(qū),From區(qū)徒探,To區(qū)和老年代內存大小比例為:8:1:1:20


堆區(qū)域劃分
堆大小參數:

-Xms:堆的最小值瓦呼;
-Xmx:堆的最大值;
-Xmn:新生代的大胁獍怠央串;
-XX:NewSize;新生代最小值碗啄;
-XX:MaxNewSize:新生代最大值蹋辅;
例如- Xmx256m

虛擬機配置查看官網:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BABHDABI
可以了解更多虛擬機配置

直接內存

直接內存不是虛擬機運行時數據區(qū)的一部分,也不是java虛擬機規(guī)范中定義的內存區(qū)域挫掏;如果使用了NIO,這塊區(qū)域會被頻繁使用侦另,在java堆內可以用directByteBuffer對象直接引用并操作;這塊內存不受java堆大小限制尉共,但受本機總內存的限制褒傅,可以通過-XX:MaxDirectMemorySize來設置(默認與堆內存最大值一樣),所以也會出現OOM異常袄友。


JVM內存區(qū)域模型.png

從底層深入理解運行時數據區(qū)

開啟HSDB工具
Jdk1.8啟動JHSDB的時候必須將sawindbg.dll復制到對應目錄的jre下


image.png

C:\Program Files\Java\jdk1.8.0_101\lib
執(zhí)行 java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB


image.png

當我們通過 Java 運行以上代碼時殿托,JVM 的整個處理過程如下:
  1. JVM 向操作系統(tǒng)申請內存,JVM 第一步就是通過配置參數或者默認配置參數向操作系統(tǒng)申請內存空間剧蚣。
  2. JVM 獲得內存空間后支竹,會根據配置參數分配堆、棧以及方法區(qū)的內存大小鸠按。
  3. 完成上一個步驟后礼搁, JVM 首先會執(zhí)行構造器,編譯器會在.java 文件被編譯成.class 文件時目尖,收集所有類的初始化代碼馒吴,包括靜態(tài)變量賦值語句、靜態(tài)代碼塊、靜態(tài)方法饮戳,靜態(tài)變量和常量放入方法區(qū)
  4. 執(zhí)行方法豪治。啟動 main 線程,執(zhí)行 main 方法扯罐,開始執(zhí)行第一行代碼负拟。此時堆內存中會創(chuàng)建一個 Teacher 對象,對象引用 student 就存放在棧中歹河。具體的操作如上面的字節(jié)碼操作過程齿椅。

深入辨析堆和棧

功能

1.以棧幀的方式存儲方法調用的過程,并存儲方法調用過程中基本數據類型的變量(int启泣、short涣脚、long、byte寥茫、float遣蚀、double、boolean纱耻、char等)以及對象的引用變量芭梯,其內存分配在棧上,變量出了作用域就會自動釋放弄喘;
2.而堆內存用來存儲Java中的對象玖喘。無論是成員變量,局部變量蘑志,還是類變量累奈,它們指向的對象都存儲在堆內存中;

線程獨享還是共享

1.棧內存歸屬于單個線程急但,每個線程都會有一個棧內存澎媒,其存儲的變量只能在其所屬線程中可見,即棧內存可以理解成線程的私有內存波桩。
2.堆內存中的對象對所有線程可見戒努。堆內存中的對象可以被所有線程訪問。

空間大小

棧的內存要遠遠小于堆內存

內存溢出

棧溢出

HotSpot版本中棧的大小是固定的镐躲,是不支持拓展的储玫。
java.lang.StackOverflowError 一般的方法調用是很難出現的,如果出現了可能會是無限遞歸萤皂。
虛擬機棧帶給我們的啟示:方法的執(zhí)行因為要打包成棧楨撒穷,所以天生要比實現同樣功能的循環(huán)慢,所以樹的遍歷算法中:遞歸和非遞歸(循環(huán)來實現)都有存在的意義敌蚜。遞歸代碼簡潔桥滨,非遞歸代碼復雜但是速度較快。
OutOfMemoryError:不斷建立線程弛车,JVM申請棧內存齐媒,機器沒有足夠的內存。

堆溢出

內存溢出:申請內存空間,超出最大堆內存空間纷跛。

方法區(qū)溢出

1.運行時常量池溢出
2.方法區(qū)中保存的Class對象沒有被及時回收掉或者Class信息占用的內存超過了我們配置喻括。
注意Class要被回收,條件比較苛刻(僅僅是可以贫奠,不代表必然唬血,因為還有一些參數可以進行控制):
(1)該類所有的實例都已經被回收,也就是堆中不存在該類的任何實例唤崭。
(2)加載該類的ClassLoader已經被回收拷恨。
(3)該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法谢肾。

本機直接內存溢出

接內存的容量可以通過MaxDirectMemorySize來設置(默認與堆內存最大值一樣)腕侄,所以也會出現OOM異常;由直接內存導致的內存溢出芦疏,一個比較明顯的特征是在HeapDump文件中不會看見有什么明顯的異常情況冕杠,如果發(fā)生了OOM,同時Dump文件很小酸茴,可以考慮重點排查下直接內存方面的原因分预。

虛擬機優(yōu)化技術

編譯優(yōu)化技術——方法內聯

方法內聯的優(yōu)化行為,就是把目標方法的代碼原封不動的“復制”到調用的方法中薪捍,避免真實的方法調用而已笼痹。

棧的優(yōu)化技術——棧幀之間數據的共享

在一般的模型中,兩個不同的棧幀的內存區(qū)域是獨立的酪穿,但是大部分的JVM在實現中會進行一些優(yōu)化与倡,使得兩個棧幀出現一部分重疊。(主要體現在方法中有參數傳遞的情況)昆稿,讓下面棧幀的操作數棧和上面棧幀的部分局部變量重疊在一起纺座,這樣做不但節(jié)約了一部分空間,更加重要的是在進行方法調用時就可以直接公用一部分數據溉潭,無需進行額外的參數復制傳遞了净响。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市喳瓣,隨后出現的幾起案子馋贤,更是在濱河造成了極大的恐慌,老刑警劉巖畏陕,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件配乓,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機犹芹,發(fā)現死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門崎页,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人腰埂,你說我怎么就攤上這事飒焦。” “怎么了屿笼?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵牺荠,是天一觀的道長。 經常有香客問我驴一,道長休雌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任肝断,我火速辦了婚禮杈曲,結果婚禮上,老公的妹妹穿的比我還像新娘孝情。我一直安慰自己鱼蝉,他們只是感情好,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布箫荡。 她就那樣靜靜地躺著魁亦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪羔挡。 梳的紋絲不亂的頭發(fā)上洁奈,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機與錄音绞灼,去河邊找鬼利术。 笑死,一個胖子當著我的面吹牛低矮,可吹牛的內容都是我干的印叁。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼军掂,長吁一口氣:“原來是場噩夢啊……” “哼轮蜕!你這毒婦竟也來了?” 一聲冷哼從身側響起蝗锥,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤跃洛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后终议,有當地人在樹林里發(fā)現了一具尸體汇竭,經...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡葱蝗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了细燎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片两曼。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖找颓,靈堂內的尸體忽然破棺而出合愈,到底是詐尸還是另有隱情叮贩,我是刑警寧澤击狮,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站益老,受9級特大地震影響彪蓬,放射性物質發(fā)生泄漏。R本人自食惡果不足惜捺萌,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一档冬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桃纯,春花似錦酷誓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至伞梯,卻和暖如春玫氢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谜诫。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工漾峡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喻旷。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓生逸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親且预。 傳聞我的和親對象是個殘疾皇子槽袄,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350