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

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


JVM作用.png

跨語言:
JVM只識別字節碼,所以JVM其實跟語言是解耦的,也就是沒有直接關聯,并不是它翻譯Java文件,而是識別class文件,這個一般稱之為字節碼。還有像Groovy 、Kotlin、Jruby等等語言,它們其實也是編譯成字節碼,所以也可以在JVM上面跑,這個就是JVM的跨語言特征。

JVM、JRE、JDK的關系

JVM只是一個翻譯,把Class翻譯成機器識別的代碼,但是需要注意,JVM 不會自己生成代碼,需要大家編寫代碼,同時需要很多依賴類庫,這個時候就需要用到JRE。
JRE是什么,它除了包含JVM之外,提供了很多的類庫(就是我們說的jar包,它可以提供一些即插即用的功能,比如讀取或者操作文件,連接網絡,使用I/O等等之類的)這些東西就是JRE提供的基礎類庫。JVM 標準加上實現的一大堆基礎類庫,就組成了 Java 的運行時環境,也就是我們常說的 JRE(Java Runtime Environment)。
但對于程序員來說,JRE還不夠。我寫完要編譯代碼,還需要調試代碼,還需要打包代碼、有時候還需要反編譯代碼。所以我們會使用JDK,因為JDK還提供了一些非常好用的小工具,比如 javac(編譯代碼)、java、jar (打包代碼)、javap(反編譯<反匯編>)等。這個就是JDK。


Java SE體系架構.png

Java程序的運行過程

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

Java程序的運行過程.png

運行時數據區域

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


運行時數據區.png
Java數據存儲結構圖

線程獨享的區域

程序計數器

小的內存空間,當前線程執行的字節碼的行號指示器;各線程之間獨立存儲,互不影響。程序計數器是一塊很小的內存空間,主要用來記錄各個線程執行的字節碼的地址,例如,分支、循環、跳轉、異常、線程恢復等都依賴于計數器。由于 Java 是多線程語言,當執行的線程數量超過 CPU 核數時,線程之間會根據時間片輪詢爭奪 CPU 資源。如果一個線程的時間片用完了,或者是其它原因導致這個線程的 CPU 資源被提前搶奪,那么這個退出的線程就需要單獨的一個程序計數器,來記錄下一條運行的指令。
程序計數器也是JVM中唯一不會OOM(OutOfMemory)的內存區域

虛擬機棧

虛擬機棧在JVM運行過程中存儲當前線程運行方法所需的數據,指令、返回地址。Java 虛擬機棧是基于線程的。哪怕你只有一個 main() 方法,也是以線程的方式運行的。在線程的生命周期中,參與計算的數據會頻繁地入棧和出棧,棧的生命周期是和線程一樣的。棧里的每條數據,就是棧幀。在每個 Java 方法被調用的時候,都會創建一個棧幀,并入棧。一旦完成相應的調用,則出棧。所有的棧幀都出棧后,線程也就結束了。

棧幀

棧幀,虛擬機棧的存儲單元,虛擬機棧中存放著一個或多個棧幀。每個棧幀,都包含四個區域:(局部變量表、操作數棧、動態連接、返回地址)。棧的大小缺省為1M,可用參數 –Xss調整大小,例如-Xss256k

局部變量表

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

操作數棧

它就是一個棧,先進后出的棧結構,操作數棧,就是用來操作的,操作的的元素可以是任意的java數據類型,所以我們知道一個方法剛剛開始的時候,這個方法的操作數棧就是空的,操作數棧運行方法就是JVM一直運行入棧/出棧的操作。在JVM中,基于解釋執行的這種方式是基于棧的引擎,這個說的棧,就是操作數棧。

動態連接

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

返回地址

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

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

字節碼的描述

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

本地方法棧

本地方法棧跟 Java 虛擬機棧的功能類似,Java 虛擬機棧用于管理 Java 函數的調用,而本地方法棧則用于管理本地方法的調用。但本地方法并不是用 Java 實現的,而是由 C 語言實現的。本地方法棧是和虛擬機棧非常相似的一個區域,它服務的對象是 native 方法。你甚至可以認為虛擬機棧和本地方法棧是同一個區域。虛擬機規范無強制規定,各版本虛擬機自由實現 ,HotSpot直接把本地方法棧和虛擬機棧合二為一

線程共享的區域

方法區/永久代

很多開發者都習慣將方法區稱為“永久代”,其實這兩者并不是等價的。HotSpot 虛擬機使用永久代來實現方法區,但在其它虛擬機中,例如,Oracle 的 JRockit、IBM 的 J9 就不存在永久代一說。因此,方法區只是 JVM 中規范的一部分,可以說,在 HotSpot 虛擬機中,設計人員使用了永久代來實現了 JVM 規范的方法區。
方法區主要是用來存放已被虛擬機加載的類相關信息,包括類信息、靜態變量、常量、運行時常量池、字符串常量池。
JVM 在執行某個類的時候,必須先加載。在加載類(加載、驗證、準備、解析、初始化)的時候,JVM 會先加載 class 文件,而在 class 文件中除了有類的版本、字段、方法和接口等描述信息外,還有一項信息是常量池 (Constant Pool Table),用于存放編譯期間生成的各種字面量和符號引用。
字面量包括字符串(String a=“b”)、基本類型的常量(final 修飾的變量),符號引用則包括類和方法的全限定名(例如 String 這個類,它的全限定名就是 Java/lang/String)、字段的名稱和描述符以及方法的名稱和描述符。而當類加載到內存中后,JVM 就會將 class 文件常量池中的內容存放到運行時的常量池中;在解析階段JVM 會把符號引用替換為直接引用(對象的索引值)。例如,類中的一個字符串常量在 class 文件中時,存放在 class 文件常量池中的;在 JVM 加載完類之后,JVM 會將這個字符串常量放到運行時常量池中,并在解析階段,指定該字符串對象的索引值。運行時常量池是全局共享的,多個類共用一個運行時常量池,class 文件中常量池多個相同的字符串在運行時常量池只會存在一份。
方法區與堆空間類似,也是一個共享內存區,所以方法區是線程共享的。假如兩個線程都試圖訪問方法區中的同一個類信息,而這個類還沒有裝入 JVM,那么此時就只允許一個線程去加載它,另一個線程必須等待。在 HotSpot 虛擬機、Java7 版本中已經將永久代的靜態變量和運行時常量池轉移到了堆中,其余部分則存儲在 JVM 的非堆內存中,而 Java8 版本已經將方法區中實現的永久代去掉了,并用元空間(class metadata)代替了之前的永久代,并且元空間的存儲位置是本地。

元空間大小參數:

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

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

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

堆是 JVM 上最大的內存區域,我們申請的幾乎所有的對象,都是在這里存儲的。我們常說的垃圾回收,操作的對象就是堆。堆空間一般是程序啟動時,就申請了,但是并不一定會全部使用。
隨著對象的頻繁創建,堆空間占用的越來越多,就需要不定期的對不再使用的對象進行回收。這個在 Java 中,就叫作 GC(Garbage Collection)。那一個對象創建的時候,到底是在堆上分配,還是在棧上分配呢?這和兩個方面有關:對象的類型和在 Java 類中存在的位置。
在常見的虛擬機中,將堆分為Eden區,From區,To區和老年代四塊區域。
Eden區,From區,To區和老年代內存大小比例為:8:1:1:20


堆區域劃分
堆大小參數:

-Xms:堆的最小值;
-Xmx:堆的最大值;
-Xmn:新生代的大小;
-XX:NewSize;新生代最小值;
-XX:MaxNewSize:新生代最大值;
例如- Xmx256m

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

直接內存

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


JVM內存區域模型.png

從底層深入理解運行時數據區

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


image.png

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


image.png

當我們通過 Java 運行以上代碼時,JVM 的整個處理過程如下:
  1. JVM 向操作系統申請內存,JVM 第一步就是通過配置參數或者默認配置參數向操作系統申請內存空間。
  2. JVM 獲得內存空間后,會根據配置參數分配堆、棧以及方法區的內存大小。
  3. 完成上一個步驟后, JVM 首先會執行構造器,編譯器會在.java 文件被編譯成.class 文件時,收集所有類的初始化代碼,包括靜態變量賦值語句、靜態代碼塊、靜態方法,靜態變量和常量放入方法區
  4. 執行方法。啟動 main 線程,執行 main 方法,開始執行第一行代碼。此時堆內存中會創建一個 Teacher 對象,對象引用 student 就存放在棧中。具體的操作如上面的字節碼操作過程。

深入辨析堆和棧

功能

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

線程獨享還是共享

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

空間大小

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

內存溢出

棧溢出

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

堆溢出

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

方法區溢出

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

本機直接內存溢出

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

虛擬機優化技術

編譯優化技術——方法內聯

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

棧的優化技術——棧幀之間數據的共享

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380