java JVM的內存區域(運行時數據區域)

JVM的內存形式: html

(1)方法區:存放了要加載的類的信息(名稱,修飾符等)、類中的靜態變量、類中定義爲final的變量、類中Field信息、類中的方法信息,當開發人員經過Class對象的getName、isInterface方法來獲取信息時候,這些信息都來源於方法區。方法區域也是全局共享的,在必定條件下它也會被GC,當方法區域要使用的內存超過其運行的大小時,會拋出OutOfMemory信息。調節大小使用-XX:PerSize和-XX:MaxPerSize java

(2)堆:用於存入對象實例和數組值,全部的線程共享堆空間。 程序員

注意建立出來的對象只包含屬於各自的成員變量,並不包括成員方法。由於同一個類的對象擁有各自的成員變量,存儲在各自的堆中,可是他們共享該類的方法,並非每建立一個對象就把成員方法複製一次。對象使用方法的時候方法才被壓入棧,方法不使用則不佔用內存。調節大小使用:-Xms和-Xmx 算法

(3)本地方法棧:用於支持native方法的執行,存儲了每一個native方法調用的狀態。 windows

(4)PC寄存器和JVM方法棧:每一個線程會建立本身的PC寄存器和JVM方法棧,因此PC寄存器和JVM方法棧爲線程所私有。PC存放下一條指令在方法內的偏移量,棧中存放棧幀,每一個方法每次調用都會產生棧幀,棧幀主要是分爲局部變量區和操做數棧兩個部分。局部變量區用於存放方法中的局部變量和參數,操做數棧存放方法執行過程當中產生的中間結果。棧幀裏面還有一些雜用空間,例如方法已經解析常量池的引用。當JVM方法棧空間不足時,會拋出StackOverflowError錯誤。 數組


  本文引用自:深刻理解Java虛擬機的第2章內容(全面) 安全

  Java與C++之間有一堵由內存動態分配和垃圾收集技術所圍成的高牆,牆外面的人想進去,牆裏面的人卻想出來。 服務器

  概述:


  對於從事C和C++程序開發的開發人員來講,在內存管理領域,他們既是擁有最高權力的皇帝,又是從事最基礎工做的勞動人民—既擁有每 數據結構

一個對象的「全部權」,又擔負着每個對象生命開始到終結的維護責任。 多線程

  對於Java程序員來講,在虛擬機的自動內存管理機制的幫助下,再也不須要爲每個new操做去寫配對的delete/free代碼,並且不容易出現

內存泄漏和內存溢出問題,看起來由虛擬機管理內存一切都很美好。不過,也正是由於Java程序員把內存控制的權力交給了Java虛擬機,一旦

出現內存泄漏和溢出方面的問題,若是不瞭解虛擬機是怎樣使用內存的,那排查錯誤將會成爲一項異常艱難的工做。

  運行時數據區域


  Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域。這些區域都有各自的用途,以及建立和銷燬的時

間,有的區域隨着虛擬機進程的啓動而存在,有些區域則是依賴用戶線程的啓動和結束而創建和銷燬。根據《Java虛擬機規範(第2版)》的規

定,Java虛擬機所管理的內存將會包括如下幾個運行時數據區域,以下圖所示:

          

  程序計數器     

  程序計數器(Program Counter Register)是一塊較小的內存空間,它的做用能夠看作是當前線程所執行的字節碼的行號指示器。在虛擬

機的概念模型裏(僅是概念模型,各類虛擬機可能會經過一些更高效的方式去實現),字節碼解釋器工做時就是經過改變這個計數器的值來選取

下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。 因爲Java虛擬機的多線

程是經過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器(對於多核處理器來講是一個內

核)只會執行一條線程中的指令。所以,爲了線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各條

線程之間的計數器互不影響,獨立存儲,咱們稱這類內存區域爲「線程私有」的內存。 若是線程正在執行的是一個Java方法,這個計數器

記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行的是Natvie方法,這個計數器值則爲空(Undefined)。此內存區域是惟一一個

在Java虛擬機規範中沒有規定任何OutOfMemoryError狀況的區域。

  Java虛擬機棧

  與程序計數器同樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命週期與線程相同。虛擬機棧描述的是Java

方法執行的內存模型:每一個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於存儲局部變量表、操做棧、動態連接、方法出口

等信息。每個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。

  常常有人把Java內存區分爲堆內存(Heap)和棧內存(Stack),這種分法比較粗糙,Java內存區域的劃分實際上遠比這複雜。這種劃分

方式的流行只能說明大多數程序員最關注的、與對象內存分配關係最密切的內存區域是這兩塊。其中所指的「堆」在後面會專門講述,而所指

的「棧」就是如今講的虛擬機棧,或者說是虛擬機棧中的局部變量表部分。

  局部變量表存放了編譯期可知的各類基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用

(reference類型),它不等同於對象自己,根據不一樣的虛擬機實現,它多是一個指向對象起始地址的引用指針,也可能指向一個表明對象的

句柄或者其餘與此對象相關的位置)和returnAddress類型(指向了一條字節碼指令的地址)。

  其中64位長度的long和double類型的數據會佔用2個局部變量空間(Slot),其他的數據類型只佔用1個。局部變量表所需的內存

空間在編譯期間完成分配,當進入一個方法時,這個方法須要在幀中分配多大的局部變量空間是徹底肯定的,在方法運行期間不會改

變局部變量表的大小。 在Java虛擬機規範中,對這個區域規定了兩種異常情況:若是線程請求的棧深度大於虛擬機所容許的深度,將拋出

StackOverflowError異常;若是虛擬機棧能夠動態擴展(當前大部分的Java虛擬機均可動態擴展,只不過Java虛擬機規範中也容許固定長度的

虛擬機棧),當擴展時沒法申請到足夠的內存時會拋出OutOfMemoryError異常。

  本地方法棧

  本地方法棧(Native Method Stacks)與虛擬機棧所發揮的做用是很是類似的,其區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字

節碼)服務,而本地方法棧則是爲虛擬機使用到的Native方法服務。虛擬機規範中對本地方法棧中的方法使用的語言、使用方式與數據結構並沒

有強制規定,所以具體的虛擬機能夠自由實現它。甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二爲一。與

虛擬機棧同樣,本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常。

  Java堆

  對於大多數應用來講,Java堆(Java Heap)是Java虛擬機所管理的內存中最大的一塊。Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一目的就是存放對象實例,幾乎全部的對象實例都在這裏分配內存。這一點在Java虛擬機規範中的描述是:全部的對象實例以及數組都要在堆上分配,可是隨着JIT編譯器的發展與逃逸分析技術的逐漸成熟,棧上分配、標量替換優化技術將會致使一些微妙的變化發生,全部的對象都分配在堆上也漸漸變得不是那麼「絕對」了。

  Java堆是垃圾收集器管理的主要區域,所以不少時候也被稱作「GC堆」(Garbage Collected Heap,幸虧國內沒翻譯成「垃圾堆」)。若是從內存回收的角度看,因爲如今收集器基本都是採用的分代收集算法,因此Java堆中還能夠細分爲:新生代和老年代;再細緻一點的有Eden空間、From Survivor空間、To Survivor空間等。若是從內存分配的角度看,線程共享的Java堆中可能劃分出多個線程私有的分配緩衝區(Thread Local Allocation Buffer,TLAB)。不過,不管如何劃分,都與存放內容無關,不管哪一個區域,存儲的都仍然是對象實例,進一步劃分的目的是爲了更好地回收內存,或者更快地分配內存。在本章中,咱們僅僅針對內存區域的做用進行討論,Java堆中的上述各個區域的分配和回收等細節將會是下一章的主題。

  根據Java虛擬機規範的規定,Java堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可,就像咱們的磁盤空間同樣。在實現時,既能夠實現成固定大小的,也能夠是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現的(經過-Xmx和-Xms控制)。若是在堆中沒有內存完成實例分配,而且堆也沒法再擴展時,將會拋出OutOfMemoryError異常。

  方法區

  方法區(Method Area)與Java堆同樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分,可是它卻有一個別名叫作Non-Heap(非堆),目的應該是與Java堆區分開來。

  對於習慣在HotSpot虛擬機上開發和部署程序的開發者來講,不少人願意把方法區稱爲「永久代」(Permanent Generation),本質上二者並不等價,僅僅是由於HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區,或者說使用永久代來實現方法區而已。對於其餘虛擬機(如BEA JRockit、IBM J9等)來講是不存在永久代的概念的。即便是HotSpot虛擬機自己,根據官方發佈的路線圖信息,如今也有放棄永久代並「搬家」至Native Memory來實現方法區的規劃了。

  Java虛擬機規範對這個區域的限制很是寬鬆,除了和Java堆同樣不須要連續的內存和能夠選擇固定大小或者可擴展外,還能夠選擇不實現垃圾收集。相對而言,垃圾收集行爲在這個區域是比較少出現的,但並不是數據進入了方法區就如永久代的名字同樣「永久」存在了。這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載,通常來講這個區域的回收「成績」比較難以使人滿意,尤爲是類型的卸載,條件至關苛刻,可是這部分區域的回收確實是有必要的。在Sun公司的BUG列表中,曾出現過的若干個嚴重的BUG就是因爲低版本的HotSpot虛擬機對此區域未徹底回收而致使內存泄漏。 根據Java虛擬機規範的規定,當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError異常。

  運行時常量池

  運行時常量池(Runtime Constant Pool)是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後存放到方法區的運行時常量池中。 Java虛擬機對Class文件的每一部分(天然也包括常量池)的格式都有嚴格的規定,每個字節用於存儲哪一種數據都必須符合規範上的要求,這樣纔會被虛擬機承認、裝載和執行。但對於運行時常量池,Java虛擬機規範沒有作任何細節的要求,不一樣的提供商實現的虛擬機能夠按照本身的須要來實現這個內存區域。不過,通常來講,除了保存Class文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運行時常量池中。 運行時常量池相對於Class文件常量池的另一個重要特徵是具有動態性,Java語言並不要求常量必定只能在編譯期產生,也就是並不是預置入Class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的即是String類的intern()方法。 既然運行時常量池是方法區的一部分,天然會受到方法區內存的限制,當常量池沒法再申請到內存時會拋出OutOfMemoryError異常。

  對象訪問


  介紹完Java虛擬機的運行時數據區以後,咱們就能夠來探討一個問題:在Java語言中,對象訪問是如何進行的?對象訪問在Java語言中無處不在,是最普通的程序行爲,但即便是最簡單的訪問,也會卻涉及Java棧、Java堆、方法區這三個最重要內存區域之間的關聯關係,以下面的這句代碼:

          Object obj = new Object();

假設這句代碼出如今方法體中,那「Object obj」這部分的語義將會反映到Java棧的本地變量表中,做爲一個reference類型數據出現。而「new Object()」這部分的語義將會反映到Java堆中,造成一塊存儲了Object類型全部實例數據值(Instance Data,對象中各個實例字段的數據)的結構化內存,根據具體類型以及虛擬機實現的對象內存佈局(Object Memory Layout)的不一樣,這塊內存的長度是不固定的。另外,在Java堆中還必須包含能查找到此對象類型數據(如對象類型、父類、實現的接口、方法等)的地址信息,這些類型數據則存儲在方法區中。

  因爲reference類型在Java虛擬機規範裏面只規定了一個指向對象的引用,並無定義這個引用應該經過哪一種方式去定位,以及訪問到Java堆中的對

象的具體位置,所以不一樣虛擬機實現的對象訪問方式會有所不一樣,主流的訪問方式有兩種:使用句柄和直接指針。 若是使用句柄訪問方式,Java堆中將會劃分出一塊內存來做爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據和類型數據各自的具體地址信息,以下圖所示:

      

  若是使用的是直接指針訪問方式,Java 堆對象的佈局中就必須考慮如何放置訪問類型數據的相關信息,reference中直接存儲的就是對象地址,以下

圖所示:

      

  這兩種對象的訪問方式各有優點,使用句柄訪問方式的最大好處就是reference中存儲的是穩定的句柄地址,在對象被移動(垃圾收集時移動對象是很是廣泛的行爲)時只會改變句柄中的實例數據指針,而reference自己不須要被修改。使用直接指針訪問方式的最大好處就是速度更快,它節省了一次指針定位的時間開銷,因爲對象的訪問在Java中很是頻繁,所以這類開銷聚沙成塔後也是一項很是可觀的執行成本。就本書討論的主要虛擬機Sun HotSpot而言,它是使用第二種方式進行對象訪問的,但從整個軟件開發的範圍來看,各類語言和框架使用句柄來訪問的狀況也十分常見。


問題:

問題一:對象引用的內存結構是什麼?

在Java中,對象的引用其實是指向一個句柄的指針,該句柄包含一對指針,一個指針指向一張表,實際上該表也有兩個指針(一個指針指向了包含了對象的方法表,另外一個指向了對象類型,代表該對象所屬的類型),另外一個指針指向一塊從Java堆中分配的內存空間,該空間用於存儲對象數據。

參考:

Java內存管理:深刻Java內存區域:http://www.cnblogs.com/gw811/archive/2012/10/18/2730117.html(詳細)

運行時數據區:http://bhsc-happy.iteye.com/blog/332723

  1. 當Java虛擬機運行時,它須要內存來存取不少東西。例如,字節碼,從已經裝載的class文件中獲得的其餘信息,程序建立的對象,傳遞給方法的參數,返回值,局部變量,已經運算的中間結果等。Java虛擬機把這些數據都組織到幾個「運行時數據區」,以便於管理,主要包括方法區、堆、Java棧、PC寄存器、本地方法棧。
  2. 方法區
  3. 在Java虛擬機中,關於被裝載的類型的信息存儲在一個邏輯上被稱爲方法區的內存中。當虛擬機裝載某個類型時,它使用類裝載器定位相應的class文件,而後讀入這個class文件--------一個線性二進制數據流--------而後把它傳輸到虛擬機中。緊接着虛擬機提取其中的類型信息,並將這些信息存儲到方法區。該類型中的類(靜態)變量一樣也存儲在方法區中。
  4. 因爲全部線程都共享方法區,所以它們對方法區數據的訪問必須被設計是線程安全的。好比,假設同時兩個線程都企圖訪問一個名爲Lava的類,而這個類尚未內裝載入虛擬機,那麼,這時應該只有一個線程去裝載它,而另外一個線程則只能等待。
  5. 對每一個裝載的類型,虛擬機都會在方法區中存儲一下類型信息:
  6. l 這個類型的全限定名
  7. l 這個類型的直接超類的全限定名(除了Object)
  8. l 這個類型的是類類型仍是接口類型
  9. l 這個類型的訪問修飾符
  10. l 任何直接超接口的全限定名的有序列表
  11. 除了上面的基本信息之外,虛擬機還得爲每一個被裝載的類型存儲如下信息:
  12. l 該類型的常量池
  13. l 字段信息
  14. l 方法信息
  15. l 除了常量之外的全部類變量
  16. l 一個到ClassLoader的引用
  17. l 一個到Class類的引用
  18. 指向ClassLoader的引用
  19. 每一個類型被裝載的時候,虛擬機必須跟蹤它是由啓動類裝載器仍是由用戶自定義類裝載器裝載的。若是是用戶自定義類裝載器裝載的,那麼虛擬機必須在類型信息中存儲對該類型裝載器的引用。這是做爲方法表中的類型數據的一部分保存的。
  20. 虛擬機會在動態鏈接期間使用這個信息。當某個類型引用另外一個類型的時候,虛擬機會請求裝載發起引用的類裝載器來裝載被引用的類型。這個動態鏈接的過程,對於虛擬機分離命名空間的方式也是相當重要的。爲了可以正確執行動態鏈接以及維護多個命名空間,虛擬機須要在方法表中得知每一個類都是哪一個類裝載器裝載的。
  21. 指向Class的引用
  22. 對於每一個被裝載的類型(無論是類仍是接口),虛擬機都會相應地爲它在堆上建立一個java.lang.Class類的實例,並且虛擬機還必須以某種方式把這個實例和存儲在方法區中的類型數據關聯起來。
  23. 在程序中,可使用指向Class對象的引用。Class類中的兩個方法能夠獲得任何已裝載的類的Class實例的引用。分別是Class.forName(「」)以及Class.getClass()。
  24. 給出一個Class對象的引用,就能夠經過Class類中的定義的方法來找出這個類型的相關信息。若是查看這些方法會很快意思到,Class類使得運行程序能夠訪問方法區中保存的信息。下面的是Class類中聲明的方法:
  25. public string getName(); 返回類的全限定名
  26. public Class getSuperClass(); 返回類型的直接超類實例(object或者接口返回null)
  27. public boolean isInterface(); 判斷該類型是不是接口
  28. public Class[] getInterfaces(); 返回一個Class對象數組,每一個Class對象對應一個直接超類接口,若是該類型沒有直接超接口,getInterfaces()返回一個0長度數組
  29. public ClassLoader getClassLoader(); 返回該類型的ClassLoader對象的引用
  30. Java程序在運行時建立的全部類實或數組都放在同一個堆中。而一個Java虛擬實例中只存在一個堆空間,所以全部線程都將共享這個堆。
  31. Java對象中包含的基本數據由他所屬的類及其全部超類聲明的實例變量組成。只要有一個對象引用,虛擬機就必須可以快速的定位對象實例的數據。另外,它也必須能經過該對象引用訪問相應的類數據(存儲於方法區的類型信息)。所以在對象中一般會有一個指向方法區的指針。
  32. 程序計數器
  33. 每一個運行中的Java程序,每個線程都有它本身的PC寄存器,也是該線程啓動時建立的。PC寄存器的內容老是指向下一條將被執行指令的餓「地址」,這裏的「地址」能夠是一個本地指針,也能夠是在方法區中相對應於該方法起始指令的偏移量。
  34. Java棧
  35. Java棧以幀爲單位保存線程的運行狀態。虛擬機只會直接對Java棧執行兩種操做:以幀爲單位的壓棧或出棧。
  36. 和方法區和堆同樣,Java棧和幀在內存中也沒必要是連續的。幀能夠分佈在連續的棧裏,也能夠分佈在堆裏,或者二者兼有的。
  37. 棧幀
  38. 棧幀由三部分組成:局部變量、操做數棧和幀數據區。
  39. 當虛擬機調用一個Java方法時,它從對應類的而類型信息中獲得此方法的局部變量區和操做數棧的大小,並據此分配棧幀內存,而後壓入Java棧中。
  40. 局部變量:存放局部變量和方法參數
  41. 操做數棧:線程的工做區,用來存放運算過程當中的臨時數據
  42. 幀數據區:保存支持常量解析、正常方法返回以及異常派發機制的數據
當Java虛擬機運行時,它須要內存來存取不少東西。例如,字節碼,從已經裝載的class文件中獲得的其餘信息,程序建立的對象,傳遞給方法的參數,返回值,局部變量,已經運算的中間結果等。Java虛擬機把這些數據都組織到幾個「運行時數據區」,以便於管理,主要包括方法區、堆、Java棧、PC寄存器、本地方法棧。
 
 
方法區
 
      在Java虛擬機中,關於被裝載的類型的信息存儲在一個邏輯上被稱爲方法區的內存中。當虛擬機裝載某個類型時,它使用類裝載器定位相應的class文件,而後讀入這個class文件--------一個線性二進制數據流--------而後把它傳輸到虛擬機中。緊接着虛擬機提取其中的類型信息,並將這些信息存儲到方法區。該類型中的類(靜態)變量一樣也存儲在方法區中。
 
       因爲全部線程都共享方法區,所以它們對方法區數據的訪問必須被設計是線程安全的。好比,假設同時兩個線程都企圖訪問一個名爲Lava的類,而這個類尚未內裝載入虛擬機,那麼,這時應該只有一個線程去裝載它,而另外一個線程則只能等待。
 
       對每一個裝載的類型,虛擬機都會在方法區中存儲一下類型信息:
 
l  這個類型的全限定名
l  這個類型的直接超類的全限定名(除了Object)
l  這個類型的是類類型仍是接口類型
l  這個類型的訪問修飾符
l  任何直接超接口的全限定名的有序列表
除了上面的基本信息之外,虛擬機還得爲每一個被裝載的類型存儲如下信息:
 
l  該類型的常量池
l  字段信息
l  方法信息
l  除了常量之外的全部類變量
l  一個到ClassLoader的引用
l  一個到Class類的引用
 
      
指向ClassLoader的引用
 
       每一個類型被裝載的時候,虛擬機必須跟蹤它是由啓動類裝載器仍是由用戶自定義類裝載器裝載的。若是是用戶自定義類裝載器裝載的,那麼虛擬機必須在類型信息中存儲對該類型裝載器的引用。這是做爲方法表中的類型數據的一部分保存的。
 
       虛擬機會在動態鏈接期間使用這個信息。當某個類型引用另外一個類型的時候,虛擬機會請求裝載發起引用的類裝載器來裝載被引用的類型。這個動態鏈接的過程,對於虛擬機分離命名空間的方式也是相當重要的。爲了可以正確執行動態鏈接以及維護多個命名空間,虛擬機須要在方法表中得知每一個類都是哪一個類裝載器裝載的。
 
 
 
指向Class的引用
 
對於每一個被裝載的類型(無論是類仍是接口),虛擬機都會相應地爲它在堆上建立一個java.lang.Class類的實例,並且虛擬機還必須以某種方式把這個實例和存儲在方法區中的類型數據關聯起來。
 
       在程序中,可使用指向Class對象的引用。Class類中的兩個方法能夠獲得任何已裝載的類的Class實例的引用。分別是Class.forName(「」)以及Class.getClass()。
 
       給出一個Class對象的引用,就能夠經過Class類中的定義的方法來找出這個類型的相關信息。若是查看這些方法會很快意思到,Class類使得運行程序能夠訪問方法區中保存的信息。下面的是Class類中聲明的方法:
 
public string getName();                     返回類的全限定名
public Class getSuperClass();        返回類型的直接超類實例(object或者接口返回null)
public boolean isInterface();         判斷該類型是不是接口
public Class[] getInterfaces();              返回一個Class對象數組,每一個Class對象對應一個直接超類接口,若是該類型沒有直接超接口,getInterfaces()返回一個0長度數組
public ClassLoader getClassLoader();    返回該類型的ClassLoader對象的引用
 
 
 
堆
 
Java程序在運行時建立的全部類實或數組都放在同一個堆中。而一個Java虛擬實例中只存在一個堆空間,所以全部線程都將共享這個堆。
 
Java對象中包含的基本數據由他所屬的類及其全部超類聲明的實例變量組成。只要有一個對象引用,虛擬機就必須可以快速的定位對象實例的數據。另外,它也必須能經過該對象引用訪問相應的類數據(存儲於方法區的類型信息)。所以在對象中一般會有一個指向方法區的指針。
 
程序計數器
 
       每一個運行中的Java程序,每個線程都有它本身的PC寄存器,也是該線程啓動時建立的。PC寄存器的內容老是指向下一條將被執行指令的餓「地址」,這裏的「地址」能夠是一個本地指針,也能夠是在方法區中相對應於該方法起始指令的偏移量。
 
Java棧
 
       Java棧以幀爲單位保存線程的運行狀態。虛擬機只會直接對Java棧執行兩種操做:以幀爲單位的壓棧或出棧。
 
       和方法區和堆同樣,Java棧和幀在內存中也沒必要是連續的。幀能夠分佈在連續的棧裏,也能夠分佈在堆裏,或者二者兼有的。
 
       棧幀
 
       棧幀由三部分組成:局部變量、操做數棧和幀數據區。
 
       當虛擬機調用一個Java方法時,它從對應類的而類型信息中獲得此方法的局部變量區和操做數棧的大小,並據此分配棧幀內存,而後壓入Java棧中。
 
       局部變量:存放局部變量和方法參數
       操做數棧:線程的工做區,用來存放運算過程當中的臨時數據
       幀數據區:保存支持常量解析、正常方法返回以及異常派發機制的數據

Java內存區域與內存溢出異常:http://raging-sweet.iteye.com/blog/1166888(壓縮版本)

參考:http://www.cnblogs.com/Cratical/archive/2012/08/21/2649985.html

  1. 運行時數據區域:
  2. 包括 方法區,虛擬機棧,本地方法棧,堆 和程序計數器。
  3. 程序計數器:
  4. 是一塊較小的內存空間,它的做用能夠看做是當前線程所執行的字節碼的行號指示器。
  5. 每個線程都有本身私有的程序計數器。
  6. 若是線程正在執行的是一個JAVA方法,該計數器記錄的是正在執行的虛擬機字節碼指令的地址,若是正在執行的是native方法,則計數器值爲空(undefined)。此內存區域是惟一一個在JAVA虛擬機規範中沒有規定任何OutOfMemoryError狀況的區域。
  7. JAVA虛擬機棧:
  8. 也是線程私有的,生命週期和線程相同。每一個方法被執行的時候都會同時建立一個棧幀(stack frame)用於存儲局部變量表、操做棧、動態連接、方法出口等信息。每一個方法被調用直至執行完成的過程,就對應一個棧幀在虛擬機棧中從入棧道出棧的過程。
  9. 這個JAVA虛擬機棧就是咱們常說的「棧」。局部變量表存放了元數據類型、引用類型
  10. 局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法須要在幀中分配多大的局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表的大小。
  11. 在JVM規範中,對這個區域規定了兩種異常狀況:
  12. 若是線程請求的棧深度大於JVM容許的深度,拋出StackOverflowError
  13. 若是虛擬機棧能夠動態擴展(目前大部分JVM均可動態擴展),當擴展時沒法申請到足夠的內存時,會拋出OutOfMemoryError異常
  14. 本地方法棧:
  15. 與虛擬機棧做用相似,只不過前者是執行JAVA方法服務,而本地方法棧是爲Native方法服務。
  16. HotSpot將本地方法棧和虛擬機棧合二爲一。
  17. 和虛擬機棧同樣會拋出來兩種異常。
  18. JAVA堆:
  19. JAVA堆是被全部線程共享的一塊內存區域,在JVM啓動時建立。其做用就是存放對象實例。
  20. JVM規範規定:全部的對象實例及數組都要在堆上分配。
  21. JAVA堆也是垃圾回收管理的主要區域。
  22. 因爲如今收集器基本採用分代收集算法,因此JAVA堆還能夠細分爲:新生代和老年代,再細分還有Eden空間、From Survivor空間、To Survivor空間。
  23. 根據JVM規範,JAVA堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可,能夠是固定大小的,也能夠是可擴展的(用-Xmx和-Xms控制),若是堆中沒有內存,也沒法擴展,拋出OutOfMemoryError。
  24. 方法區:
  25. 也是全部線程共享的內存區域。它用於存放虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
  26. 在JVM規範中被描述爲堆的一部分,但卻又有一個non-heap的別名。
  27. 對於HOTSPOT虛擬機,方法區又被稱爲永久代(Permanent Generation)
  28. 這個區域同樣不須要連續的內存,能夠選擇固定大小或者擴展,還能夠選擇不實現垃圾回收。確實這個區域的數據通常不參與回收,但這些數據並不必定就是永久存在了,常量池和對類型的卸載也能夠成爲回收的目標。
  29. 拋出OutOfMemoryError
  30. 運行時常量池(方法區的一部分):
  31. 是方法區的一部分。
  32. 在編譯期生成的各類字面量和符號引用,這部份內容將在類加載後存放到方法區的運行時常量池中。
  33. 這個區域並非只有編譯時產生的常量,也有運行時產生的常量,好比String.intern()方法。
  34. 既然運行時常量池是方法區的一部分,天然會受到方法區內存的限制,當常量池沒法再申請到內存時會拋出OutOfMemoryError異常。
  35. 直接內存:
  36. Direct Memory
  37. 在JDK1.4中引入了NIO,是一種基於通道與緩衝區的I/O方式,它可使用Native函數庫直接分配堆外內存,而後經過一個存儲在JAVA堆裏面的DirectByteBuffer對象做爲這塊內存的引用進行操做。
  38. 本機直接內存的分配不會受到Java堆大小的限制,可是,既然是內存,則確定仍是會受到本機總內存(包括RAM及SWAP區或者分頁文件)的大小及處理器尋址空間的限制。服務器管理員配置虛擬機參數時,通常會根據實際內存設置-Xmx等參數信息,但常常會忽略掉直接內存,使得各個內存區域的總和大於物理內存限制(包括物理上的和操做系統級的限制),從而致使動態擴展時出現OutOfMemoryError異常。
  39. Object obj = new Object();
  40. Object obj反映到JAVA棧的本地變量表中
  41. new Object()反映到JAVA堆中,長度不肯定,在JAVA堆中還必須包含能查找到此對象類型數據(如對象類型、父類、實現的接口、方法等)的地址信息,這些類型數據則存放在方法區中。
  42. 引用訪問對象的方法主流的有兩種:
  43. 使用句柄訪問方式,JAVA堆中將會劃分出一塊內存來做爲句柄池,引用對象存儲的是對象的句柄地址,而句柄中包含着對象實例數據和類型數據各自的具體地址信息。
  44. 直接指針訪問方式。引用對象中直接存儲的就是對象地址。,在堆中的對象又有一個指針指向方法區的對象類型數據。
  45. 句柄訪問方式最大的好處是在對象被移動時(垃圾回收常常會移動對象),只用改變句柄中的實例數據指針,而引用對象不用修改。
  46. 直接指針方式最大的好處是速度更快。HOTSPOT使用這種方式。
  47. 設置堆不可擴展,將-Xms和-Xmx參數設置同樣便可。
  48. 棧溢出
  49. -Xoss參數設置本地方法棧大小(在HOTSPOT虛擬機中,因爲本地方法棧和虛擬機棧是一塊兒的,因此這個參數是無效的),-Xss參數設定棧大小。
  50. 棧溢出會拋出兩種異常stackoverflow和outofmemory,前者表示線程請求的棧深度大於虛擬機所容許的最大深度,後者表示沒法申請到足夠的內存空間。
  51. 實驗代表,單線程只會有stackoverflow,不會有outofmemory.但多線程會產生Outofmemory。
  52. 這是由於,OS分配給每一個進程的內存是有限制的,好比32位windows限制爲2GB,棧內存的總量是由2GB減去堆內存(Xmx),再減去方法區容量(MaxPermSize),由於程序計數器消耗內存很小,能夠忽略不計。而棧內存又被多個線程所瓜分,這樣在線程不少的狀況下,內存就可能會耗盡。
  53. 運行時常量池溢出
  54. 經過String.intern()方法能夠向運行時常量池增長內容,該方法會檢測常量池中是否有某個字符串,若是有就返回池中的字符串,不然將該字符串加入常量池,再返回該常量池字符串的引用。
  55. 因爲常量池分配在方法區中,能夠經過-XX:PermSize和-XX:MaxPermSize來限制方法區大小。方法區溢出的標誌就是OutOfMemoryError: PermGen Space.
  56. 方法區溢出
  57. 主要是因爲動態生成了太多的類形成的,這種溢出的實際場景好比大量JSP或動態生成JSP文件的應用(JSP第一次運行時會須要編譯成JAVA類)CGLIB等動態代理生成框架產生的動態類等
  58. 本機直接內存溢出
  59. DirectMemory容量能夠由-XX:MaxDirectMemorySize指定,若是不指定,則默認與JAVA堆最大值(-Xmx)同樣。實際上DirectByteBuffer並無直接申請分配內存,而是先計算,若是計算得知無足夠內存可分配,就拋出異常。 
相關文章
相關標籤/搜索