一背景:html
做爲java碼農,對於常見的編碼,編譯,執行比較熟悉了。更加關注框架跟業務實現,可是回頭想一想,當咱們執行java命令後究竟發生了什麼,就是咱們經過JVM與機器交互,Java經過使用Java虛擬機屏蔽了與具體平臺相關的信息,使得Java具有了一次編寫,多處運行的特性。java
JVM主要由類加載器子系統、運行時數據區(內存空間)、執行引擎以及與本地方法接口等組成。算法
由此能夠看出,類加載器把.class文件加載入內存,供之後實例化。此處與後面的內存區域有關。內存空間又跟java內存模型有關,更是牽扯出垃圾回收,OOM異常,內存參數優化等。執行引擎牽扯到指令優化就是JMM相關。因此jvm既是難點又是重點,理解了這些,纔會有豁然開朗的感受。固然我目前還在學習中,好多不理解的。好比沿着一個思路,同步背後的鎖,再底層的aqs,在底層的CAS,再底層的CPU實現機制。能夠一層層的熟悉,跟操做系統有關的了。好了,扯遠了。編程
二 JVM運行時數據區
好了,如今看看筆記整理的重點。數組
上圖分爲兩個部分:線程私有區(pc計數器、棧、本地方法棧),線程共享區(方法區、堆)。下面分別介紹。緩存
2.1程序計數器(The pc Register)安全
JVM一次能支持不少線程執行。每個JVM線程有它本身的程序計數器。PC寄存器裏保存有當前正在執行的JVM指令的地址。注意:(一個JVM的線程都正在執行當前線程的方法代碼。若是這個方法不是本地(native)方法,程序計數器包含當前被執行的JVM地址。若是線程正在執行本地(native)方法,程序計數器的值爲未定義。)上面這句話只是給出結果,不是很理解。數據結構
2.2棧(JVM Stacks)多線程
每一個JVM的線程在建立的時候,都會建立一個棧。一個棧包含不少棧楨。JVM的棧比如傳統語言C的棧,它維持(存儲)本地變量和部分結果,並在方法調用和返回中(被)使用。這個棧是一個後進先出的數據結構,因此當前正在執行的方法在棧的頂端,每當一個方法被調用時,一個新的棧幀就會被建立而後放在了棧的頂端。當方法正常返回或者發生了未捕獲的異常,棧幀就會從棧裏移除。併發
2.2.1 棧幀(stack frame)
JVM爲每一個方法調用建立一個新的棧幀並推到每一個方法調用的棧頂。當方法正常返回或者遇到了未捕獲的異常,這個棧幀將被移除。
每一個棧幀包含了:局部變量表、返回值、操做數棧、當前方法所在的類的運行時常量池引用。見下圖
2.2.2 局部變量表(Local variable array)
局部變量表包含了這個方法執行期間全部用到的變量,包括this引用,全部方法參數以及其餘的局部聲明變量。對於類方法(好比靜態方法)來講,全部方法參數下標都是從0開始,然而,對於實例方法來講這個0是留給this的。對於對象,局部變量區中永遠只有指向堆的引用。
2.2.3操做數棧(Operand stack)
方法實際運行的工做空間。每一個方法都在操做數棧和局部變量數組之間交換數據,而且壓入或者彈出其餘方法返回的結果。
2.2.3動態連接(幀數據)
每一個棧幀都包含了運行時常量池的引用。這個引用指向了這個棧幀正在執行的方法所在的類的常量池,它對動態連接提供了支持。
局部變量區和操做數棧的大小依照具體方法在編譯時就已經肯定。調用方法時會從方法區中找到對應類的類型信息,從中獲得具體方法的局部變量區和操做數棧的大小,依此分配棧幀內存,壓入Java棧。
棧基本概念結束了,能夠結合以前總結OOM異常,
JVM說明書(規範)容許棧要麼是一個固定大小,要麼動態擴展來知足計算的要求。若是JVM棧是一個固定的大小,當棧被建立的時候每個棧大小能夠自由設置。 在動態擴展狀況下,能夠控制最大最小內存。 在VM Spec中對這個區域規定了2種異常情況(如下兩種異常與JVM的棧機制有關):
若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError異常。
若是VM棧能夠動態擴展,在初始化新線程時沒有足夠內存建立棧則拋出OutOfMemoryError異常。
棧容量只由-Xss參數設定
2.3本地方法棧(Native Method Stacks)
通俗說就是供用非Java語言實現的本地方法的堆棧。並非全部的JVM都支持本地方法。不過那些支持的一般會建立出每一個線程的本地方法棧。若是提供本地方法棧,每一個線程建立時必須分配一個本地方法棧。是否是能夠理解爲java調用了C代碼,就建立一個C棧?待確認哈。
2.4堆(heap)
JVM有一個在全部線程內共享的堆。堆在虛擬機啓動的時候建立,堆是給全部類的實例和數組分配內存的運行時數據區。 數組和對象可能永遠不會存儲在棧上,由於一個棧幀並非設計爲在建立後會隨時改變大小。棧幀僅僅保存引用,這個引用指向對象或者數組在堆中的位置。與局部變量表(每一個棧幀裏)中的基本數據類型和引用不一樣,對象老是被存儲在堆裏,因此他們在方法結束後不會被移除,僅僅在垃圾收集的時候纔會被移除。堆中儲存的對象經過一個自動存儲管理系統(垃圾回收器)進行回收。 對象從不明確的被分配(JVM從不指明對象的釋放)。
插一句:堆是全部線程共享的,因此在進行實例化對象等操做時,須要解決同步問題。
爲了提升GC效率,從jdk1.2開始將堆內存作分代(Generation)處理。
一般他們的工做流程以下:
新對象和數組被分配在年輕代。
年輕代會發生Minor GC。 對象若是仍然存活,將會從eden區移到survivor區。
Major GC 一般會致使應用線程暫停,它會在2個區中移動對象,若是對象依然存活,將會從年輕代移到老年代。
當每次老年代進行垃圾收集的時候,會觸發持久代帶也進行一次收集。一樣,在發生full gc的時候他們2個也會被收集一次。
2.4.1年輕代
HotSpot JVM把年輕代分爲了三部分:1個Eden區和2個Survivor區(分別叫from和to)。默認比例爲8:1,由於年輕代中的對象基本都是朝生夕死的(80%以上),因此在年輕代的垃圾回收算法使用的是複製算法。
年輕代有關參數。
1)-XX:NewSize和-XX:MaxNewSize
用於設置年輕代的大小,建議設爲整個堆大小的1/3或者1/4,兩個值設爲同樣大。
2)-XX:SurvivorRatio
用於設置Eden和其中一個Survivor的比值,這個值也比較重要。
3)-XX:+PrintTenuringDistribution
這個參數用於顯示每次Minor GC時Survivor區中各個年齡段的對象的大小。
4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold
用於設置晉升到老年代的對象年齡的最小值和最大值,每一個對象在堅持過一次Minor GC以後,年齡就加1。
2.4.2 老年代
存放那些在歷經了Eden區和Survivor區的屢次GC後仍然存活下來的對象。
這裏有個對象分配的優化,能夠結合OOM異常的demo來看。
對於小對象的分配,會優先在線程私有的 TLAB (Thread Local Allocation Buffer)中分配(由於在堆上分配內存須要鎖定整個堆,而在TLAB上則不須要,JVM在分配對象時會盡可能在TLAB上分配,以提升效率),TLAB中建立的對象,不存在鎖甚至是CAS的開銷。TLAB佔用的空間在Eden Generation。
當對象比較大,TLAB的空間不足以放下,而JVM又認爲當前線程佔用的TLAB剩餘空間還足夠時,就會直接在Eden Generation上分配,此時是存在併發競爭的,因此會有CAS的開銷,但也還好。當對象大到Eden Generation放不下時,JVM只能嘗試去Old Generation分配,這種狀況須要儘量避免,由於一旦在Old Generation分配,這個對象就只能被Old Generation的GC或是FullGC回收了。
堆有關參數:-Xms -Xmx -Xmn 其中:old=Xmx-Xmn
2.5方法區(Method Area)
也被稱爲非堆區域(在HotSpot JVM的實現當中)
JVM的方法區是全部線程共享的,方法區相似於傳統語言編譯代碼時的存儲區域或相似於操做系統進程的文本段。他存儲內容包括:每個類的結構,如運行時常量池,字段和方法的數據;方法和構造器的代碼,如用於類,實例和接口初始化的特殊方法。這個方法區在JVM啓動的時候被建立,通常狀況下JVM不會選擇對方法區進行垃圾回收或者壓縮.在HotSpot JVM裏,方法區被稱爲永久區或者永久代(PermGen)。
全部線程都共享一樣的方法區,因此訪問方法區的數據和動態連接的過程都是線程安全的。若是兩個線程嘗試訪問一個類的字段或者方法而這個類尚未加載,這個類就必定會首先被加載並且僅僅加載一次,這2個線程也必定要等到加載完後纔會繼續執行。
2.5.4 運行時常量池(Runtime Constant Pool)
運行時常量池是類和接口運行時的常量池表,它在字節碼文件裏。它包含幾類常量。 在編譯時期識別的數值常量,在運行區識別的方法或引用字段。運行區常量池相似於傳統語言的字符表,但它比傳統字符表所存儲的範圍更廣。每個運行區常量池從方法區分配內存。當類和接口被JVM建立時相應的常量池也被建立。換句話說:當一個方法或者變量被引用時,JVM經過運行時常量區來查找方法或者變量在內存裏的實際地址。
幾種在常量池內存儲的數據類型包括:
數量值、字符串值、類引用、字段引用、方法引用
它能夠經過-XX:PermSize及-XX:MaxPermSize來進行調節。能夠結合以前OOM異常學習筆記來看。
運行區常量池包括如下異常:
拓展知識點:
1.從jdk8開始。持久代已經被元空間(Metadata )取代。
它是本地堆內存中的一部分
它能夠經過-XX:MetaspaceSize和-XX:MaxMetaspaceSize來進行調整
當到達XX:MetaspaceSize所指定的閾值後會開始進行清理該區域
若是本地空間的內存用盡了會收到java.lang.OutOfMemoryError: Metadata space的錯誤信息。
和持久代相關的JVM參數-XX:PermSize及-XX:MaxPermSize將會被忽略掉。
2.代碼緩存——在JVM裏,Java字節碼被解釋運行,可是它沒有直接運行本地代碼快。爲了提升性能,Oracle Hotspot VM會尋找字節碼的」熱點」區域,它指頻繁被執行的代碼,而後編譯成本地代碼。這些本地代碼會被保存在堆外內存的代碼緩存區。Hotspot用這種方式,盡力去選擇最合適的方式來權衡編譯本地代碼的時間和直接解釋執行代碼的時間。編譯後的代碼就是本地代碼(硬件相關的),它是由JIT(Just In Time)編譯器生成的,這個編譯器是HotSpot JVM所特有的。
*********************************總結********************************************
JVM真是博大精深啊,本次學習筆記屬於OOM引起的JVM內存背景梳理。
與之相關的有GC,參數優化,併發與內存模型,甚至還有String這樣神奇的對象,一篇寫不完,分開整理。就到這裏吧
參考併發編程網:
http://ifeve.com/jvm-runtime-data/
http://ifeve.com/jvm-yong-generation/
http://ifeve.com/jvm-permgen-where-art-thou/
http://ifeve.com/jvm-internals/
http://blog.hesey.net/2011/04/introduction-to-java-virtual-machine.html