Java深入淺出之JVM內存模型

一、虛擬機的發展

HotSpot VM(SUN)  以前使用範圍最廣的Java虛擬機

JRockit VM(BEA)   號稱”世界上最快的Java虛擬機”

J9 VM(IBM)

Dalvik VM( Google )

HotSpot VM(ORACLE) 目前使用範圍最廣的Java虛擬機

二、JVM的整體介紹

三、運行時數據區

這個是抽象概念,內部實現依賴寄存器、高速緩存、主內存(具體要分析 JVM 源碼 C++語言實現,沒必要看)
計算機的運行=指令+數據,指令用於執行方法的,數據用於存放數據和對象的。
虛擬機棧----執行 java 方法、本地方法棧---執行本地方法、程序計數器---程序執行的計數器
Java 中的數據:變量、常量、對象、數組相關。

四、程序計數器

較小的內存空間,當前線程執行的字節碼的行號指示器;各線程之間獨立存儲,互不影響(面試可能問到爲什麼需要)
如果線程正在執行的是一個 Java 方法,則指明當前線程執行的代字節碼行數
如果正在執行的是 Natvie 方法,這個計數器值則爲空(Undefined
此內存區域是唯一一個不會出現 OutOfMemoryError 情況的區域。

五、棧

棧(Stack):數據結構  入口和出口只有一個 入棧 出棧 

特點 先進後出(FIL0)

異常:

線程請求的棧深度大於虛擬機所允許的深度:StackOverflowError

JVM動態擴展時無法申請到足夠的內存時:OutOfMemoryError

六、虛擬機棧

每個線程私有的,線程在運行時,在執行每個方法的時候都會打包成一個棧幀,存儲了局部變量表,操作數棧,動態鏈接,方法出口等信息,然後放入
棧。每個時刻正在執行的當前方法就是虛擬機棧頂的棧楨。方法的執行就對應着棧幀在虛擬機棧中入棧和出棧的過程。
棧的大小缺省爲 1M,可用參數 –Xss 調整大小,例如-Xss256k
在編譯程序代碼的時候,棧幀中需要多大的局部變量表,多深的操作數棧都已經完全確定了,並且寫入到方法表的 Code 屬性之中,因此一個棧幀需要分
配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決於具體的虛擬機實現。
局部變量表:顧名思義就是局部變量的表,用於存放我們的局部變量的。首先它是一個 32 位的長度,主要存放我們的 Java 的八大基礎數據類型,一般 32
位就可以存放下,如果是 64 位的就使用高低位佔用兩個也可以存放下,如果是局部的一些對象,比如我們的 Object 對象,我們只需要存放它的一個引用
地址即可。(基本數據類型、對象引用、returnAddress 類型)
操作數據棧:存放我們方法執行的操作數的,它就是一個棧,先進後出的棧結構,操作數棧,就是用來操作的,操作的的元素可以是任意的 java 數據類
型,所以我們知道一個方法剛剛開始的時候,這個方法的操作數棧就是空的,操作數棧運行方法是會一直運行入棧/出棧的操作
動態連接:Java 語言特性多態(需要類加載、運行時才能確定具體的方法,後續有詳細的講解)
返回地址:
正常返回(調用程序計數器中的地址作爲返回)
三步曲:
恢復上層方法的局部變量表和操作數棧、
把返回值(如果有的話)壓入調用者棧幀的操作數棧中、
調整 PC 計數器的值以指向方法調用指令後面的一條指令、
異常的話(通過異常處理器表<非棧幀中的>來確定)

七、本地方法棧

各虛擬機自由實現,本地方法棧 native 方法調用 JNI 到了底層的 C/C++(c/c++可以觸發彙編語言,然後驅動硬件)

八、線程共享區域

方法區(永久代(JDK1.7和以前)、元空間(JDK1.8)) 類信息 常量 靜態變量 即時編譯期編譯後的代碼

Java堆(-Xms;-Xmx;-Xmn) 堆是需要重點關注的一塊區域,因爲涉及到內存的分配(new關鍵字,反射等)與回收(回收算法,收集器等) 幾乎所有的對象都是在堆中分配.

九、JVM各版本內存區域的變化

運行時常量池      Class 文件中的常量池(編譯器生成的各種字面量和符號引用)會在類加載後被放入這個區域。   

JDK1.6     運行時常量池在方法區中

JDK1.7     運行時常量池在堆中

JDK1.8     去永久代:使用元空間(空間大小隻受制於機器的內存)替代永久代     

永久代參數    

-XX:PermSize;-XX:MaxPermSize =100M   超過100M OOM()      

元空間參數    

-XX:MetaspaceSize; -XX:MaxMetaspaceSize

爲什麼會這樣呢?

永久代來存儲類信息、常量、靜態變量等數據不是個好主意, 很容易遇到內存溢出的問題。 對永久代進行調優是很困難的,同時將元空間與堆的垃圾回收進行了隔離,避免永久代引發的Full GC和OOM等問題;

十、直接內存

不是虛擬機運行時數據區的一部分,也不是java虛擬機規範中定義的內存區域;

如果使用了NIO,這塊區域會被頻繁使用,在java堆內可以用directByteBuffer對象直接引用並操作;

這塊內存不受java堆大小限制,但受本機總內存的限制,可以通過MaxDirectMemorySize來設置(默認與堆內存最大值一樣),所以也會出現OOM異常;

避免了在Java 堆和Native 堆中來回複製數據,能夠提高效率

十一、站在線程角度看JVM

十二、深入分析堆和棧

功能

以棧幀的方式存儲方法調用的過程,並存儲方法調用過程中基本數據類型的變量(int、short、long、byte、float、double、boolean、char等)以及對象的引用變量,其內存分配在棧上,變量出了作用域就會自動釋放;(stackoverflow,OOM)

而堆內存用來存儲Java中的對象。無論是成員變量,局部變量,還是類變量,它們指向的對象都存儲在堆內存中;

線程獨享還是共享

棧內存歸屬於單個線程,每個線程都會有一個棧內存,其存儲的變量只能在其所屬線程中可見,即棧內存可以理解成線程的私有內存。

堆內存中的對象對所有線程可見。堆內存中的對象可以被所有線程訪問。

空間大小

棧的內存要遠遠小於堆內存,棧的深度是有限制的,可能發生StackOverFlowError問題。

十三、JVM中對象的分配

十四、對象的內存佈局

十五、對象的訪問方式

句柄是一種特殊的智能指針 。句柄與普通指針的區別在於,指針包含的是引用對象的內存地址,而句柄則是由系統所管理的引用標識,該標識可以被系統重新定位到一個內存地址上。

十六、堆內存分配策略

堆進一步劃分 新生代(PSYoungGen) Eden空間   From Survivor空間 To Survivor空間 老年代(ParOldGen)

堆中參數配置:

新生代大小: -Xmn20m 表示新生代大小20m(初始和最大)

-XX:SurvivorRatio=8 表示Eden和Survivor的比值, 缺省爲8 表示 Eden:From:To= 8:1:1 2 Eden:From:To=  2:1:1

對象優先在Eden分配 大對象直接進入老年代 長期存活的對象將進入老年代 動態對象年齡判定 空間分配擔保

後續會持續更新JVM算法!!!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章