Java內存區域

運行時數據區域

   JVM再執行Java程序的過程當中會把它鎖管理的內存劃分爲若干個不一樣的數據區域。
   這些區域都有本身的用途,以及建立和銷燬的時間,有的區域隨着虛擬機進程的啓動而存在,有的區域則依賴用戶線程的啓動和結束而建立和銷燬。
這裏寫圖片描述java

程序計數器:

1·它是一塊較小的內存空間,它能夠看做是當前線程所執行的字節碼的行號指示器。在JVM中字節碼解釋器的工做就是改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程回覆等基礎功能都須要依賴這個計數器來完成。
2·在JVM的多線程機制中,爲了線程切換後能恢復到正確的執行位置,每一條線程都須要有一個獨立的程序計數器,每條線程的程序計數器互不影響,獨立存儲,是線程私有的內存。
3·若是線程正在執行一個Java方法,那麼這個計數器記錄的就是正在執行的虛擬機字節碼指令地址,若是正在執行的是Native方法,這個計數器則爲空。
4·這個內存區域是惟一一個在虛擬機規範中沒有規定任何OutOfMemoryError的區域。程序員

Java虛擬機棧:

1·與程序計數器同樣,虛擬機棧也是線程私有的,它的生命週期與線程相同。
2·虛擬機棧描述的是Java方法執行的內存模型,每一個方法執行的時候都會建立一個幀棧(Stack Frame)用於存放局部變量表、操做數棧、動態連接、方法出口等信息。(每個方法從調用直至執行完成的過程,就對應着一個幀棧在虛擬機棧中入棧到出棧的過程)。
3·大多數人把Java內存分爲堆和棧,可是其實他們口中的棧指的是局部變量表。
4·局部變量表存放了在編譯期可知的各類基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用和returnAddress類型。
5·若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError異常,若是虛擬機棧能夠動態擴展,若是擴展時沒法申請到足夠的內存,就會拋出OutOfMemoryError異常。算法

本地方法棧:

1·它與虛擬機棧的區別:虛擬機棧爲虛擬機執行Java方法服務,而本地方法則爲虛擬機使用到的Natice方法服務。
2·和虛擬機棧同樣,本地方法棧也會拋出StackOverfloError和OutOfMemoryError異常。數組

Java堆:

1·Java堆是被全部線程共享的一塊區域,在虛擬機啓動時建立
2·該內存區域惟一的做用就是存放對象的實例,幾乎全部的對象實例都在這裏分配內存,在JVM的規範中是這麼描述的:全部對象的實例以及數組都要在堆上分配。可是,隨着JIT編譯器、逃逸分析、棧上分配、標量替換優化技術的發展,上述的JVM規範也就沒有那麼絕對了。安全

拓展
JIT編譯器:即時編譯器,一句一句邊運行邊翻譯執行
逃逸分析:逃逸:就是當咱們建立出來一個對象時,該對象除了在本線程上使用,還要再別的線程上使用,那咱們就不考慮使用棧上分配了
棧上分配:對於那些線程私有的對象,將他們打散分配再棧上,而不是在堆上。優勢:函數在調用結束以後能夠本身銷燬,不須要GC的介入,提升性能。
標量替換:若是把一個Java對象拆散,根據程序訪問的狀況,將其使用到的成員變量恢復原始類型來訪問就叫作標量替換,那麼程序真正在執行的時候,可能不建立這個對象,直接建立它的若干個被這個方法使用到的成員變量。多線程

1·Java堆是垃圾收集器管理的主要區域
2·Java堆不須要連續的內存、能夠選擇固定大小或者可擴展大小
這裏寫圖片描述併發

3·全部新生成的對象都是放在年輕代。年輕代的目的就是儘量快速的收集掉那些生命週期短的對象。
4·堆又能夠分爲新生代和老年代:新生代用於存放剛建立的對象已經年輕的對象,若是對象一直沒有被回收,生存的足夠長久就會被移入老年代。
5·新生代又進一步能夠細分爲eden,survivorSpace0(s0, from space)、survivorSpace1(s1, to space)。函數

方法區:

1·方法區是被全部線程都共享的一個區域
2·它用於存放虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等
3·它和堆同樣不須要連續的內存、能夠選擇固定大小或者可擴展大小佈局

運行時常量池:

1·運行時常量池是方法區的一部分
2·Class文件中除了有類的版本、字段、方法、接口等描述信息以外,還有一項信息是常量池,用於存放編譯期生成的各類字面量和符號引用–>Class的常量池
3·運行時常量池相比於Class文件常量池的另一個重要特性是具有動態性,Java語言不要求常量必定只有在編譯的時候才能產生,也就是說並不是預置入Class文件中的常量池的內容才能才能進入方法區運行時常量池,運行期間也能夠將新的常量放入池中性能

直接內存:

·並不是JVM運行時數據區的一部分,可是這部份內存頻繁被使用


虛擬機對象:

對象的建立:

對象的建立過程圖解:

這裏寫圖片描述
    在上圖中的在堆區分配內存的時候通常會有兩種方案:
      1. 指針碰撞:首先Java堆中的內存是絕對規整的,全部用過的內存放在一邊,空閒的內存放在另一邊,中間放着一個指針做爲分界點的指示器,那麼鎖分配的內存就是把哪一個指針向空閒空間那邊挪一段與對象大小等 大的距離。
      2. 空閒列表:Java堆不是規整的,已使用的內存和空閒的內存相互交錯,虛擬機維護着一個列表,記錄哪些內存塊是可用的,在分配的時候就從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄。
      選擇哪一種分配方式是由Java堆是否規整決定的,而Java堆是否規整又是由所採用的垃圾收集器是否帶有壓縮整理功能決定的。因此,在使用 Serial、ParNew等帶Compact過程的收集器時,系統採用的分配算法是指針碰撞,而 使用CMS這種基於Mark-Sweep算法的收集器時,一般採用空閒列表。

因爲對象的建立在虛擬機中式很是頻繁的行爲,因此在併發的狀況下也並非線程安全的,爲了解決這個問題,由兩種方案:
1. 對分配內存空間的動做進行同步處理—實際上虛擬機採用CAS配上失敗重試的方式保證更新操做的原子性
2. 把內存分配的動做按照線程劃分規劃在不一樣的空間之中進行

內存分配完成以後,虛擬機將分配到的內存空間都初始化爲零值,這就是咱們爲何在一個類中定義全局變量的時候能夠不給它賦初值就直接使用。
在內存分配完成且內存初始化完成後,從虛擬機的角度看,一個新的對象已經產生,可是,從Java的角度看,對象的建立纔剛剛開始,在執行完new指令以後,通常會接着執行init方法,把對象按照程序員的意願進行初始化,這樣一個真正可用的對象纔算建立完成。

對象的內存佈局:

1·對象在內存中的佈局能夠分爲3個區域:對象頭(Header)、實例數據(Instance Data)、對齊填充(Padding)
2·對象頭包括兩部分信息,第一部分用於存儲對象自身運行時數據,好比:哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等。
3·對象頭的另一部分信息是類型指針,對象指向它元數據類型的指針,虛擬機經過這個來肯定這個對象是哪一個類的實例。

對象訪問定位:

1·經過棧上的本地變量表中的reference數據來操做堆上的具體對象。
2·reference數據類型只是規定了一個指向對象的引用,並無定義這個引用應該經過什麼方式去定位、訪問堆中的對象具體位置,對象訪問方式噎死取決於虛擬機實現而定的。
3·目前主流的兩種訪問方式有:1.句柄訪問 2.指針訪問

拓展
兩種訪問方式的區別:
      1.句柄訪問時直接在堆內存中維護着兩個指針,一個執行指向對象實例數據,另一個指向對象類型數據
      2.而指針訪問呢?在堆內存的對象實例數據中維護着一個指針,指向對象類型數據
兩種定位方式的優勢和缺點:       使用句柄最大的好處就是reference中存儲的時穩定的句柄地址,在對象被移動時只會改變句柄中的實例數據指針,而不須要改變reference        使用直接指針的優勢時訪問的速度更快,它節省了一次指針定位的時間開銷。

相關文章
相關標籤/搜索