JVM1:Java內存區域

前言

說到Java內存區域,可能很多人第一反應是“堆棧”。堆棧不是一個概念,而是兩個概念,堆和棧是兩塊不同的內存區域。簡單理解的話,

  1. 堆是用來存放對象而棧是用來執行程序的,堆的使用是需要new並配對delete/free的,在Java中是由JVM進行回收的區域,而棧是用來存儲臨時變量的。
  2. 堆內存和棧內存的這種劃分方式比較粗糙,這種劃分方式只能說明大多數程序員最關注的、與對象內存分配關係最密切的內存區域是這兩塊,Java內存區域的劃分實際上遠比這複雜。
    對於Javaer說,在虛擬機自動內存管理機制的幫助下,不再需要爲每一個new操作去配對delete/free代碼,不容易出現內存泄露和內存溢出問題。但是,也正是因爲Java把內存控制權交給了虛擬機,一旦出現內存泄露和內存溢出的問題,就難以排查,因此一個好的Javaer應該去了解虛擬機的內存區域以及會引起內存泄露和內存溢出的場景。

運行時數據區域

首先,我麼來看下JVM在執行Java程序時會將內存劃分爲哪些不同區域。依據《Java虛擬機規範(JavaSE7版)》,包括以下幾個運行時區域。
JVM運行時數據區

程序計數器(Program Counter Register)

  1. 程序計數器是一塊較小的內存,用來記錄當前線程所執行的字節碼的行號指示器;
  2. 如果線程執行的是Java方法,則記錄正在執行虛擬機字節碼指令的地址;如果是native方法,則爲空;
  3. 屬於線程私有

Java虛擬機棧

  1. JVM棧生命週期與線程相同;
  2. 線程方法的執行相當於棧幀的入棧和出棧過程。方法執行時都會創建棧幀,棧幀是用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息;
  3. 棧的大小和具體JVM的實現有關,通常在256K~756K之間;
  4. 屬於線程私有;
  5. 若線程請求的棧深度大於虛擬機所允許的深度,拋出StackOverflowError異常;若虛擬機可以動態擴展,但是擴展時無法申請到足夠的內存,就拋出OutOfMemoryError異常

本地方法棧

  1. 與Java虛擬機棧類似;
  2. 爲虛擬機執行native方法提供服務;
  3. 虛擬機規範並沒有對這個區域有強制規定,比如我們使用的HotSpot虛擬機,就乾脆沒有這塊區了,它和虛擬機棧是一起的;
  4. 屬於線程私有;
  5. 會拋出StackOverflowErrorOutOfMemoryError異常

  1. Java虛擬機所管理的內存中最大的區域,在虛擬機啓動時創建,唯一目的是存放對象實例;
  2. 虛擬機規範描述:幾乎所有的對象和數組都在堆上進行分配,但是隨着JIT編譯器的發展以及逃逸分析技術逐漸成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化,所有對象都分配堆上已經不是“絕對”的;
  3. 堆是JVM垃圾蒐集器管理的主要區域,可分爲新生代和老年代;更細緻的分可以分爲Eden空間、From Survivor空間及To Survivor空間;
  4. 堆是可擴展的,通過-Xmx和-Xms來控制;
  5. 屬於線程共享;
  6. 如果堆中沒有內存完成實例分配且無法再擴展,則拋出OutOfMemoryError異常

方法區

  1. 存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據;
  2. 當方法區無法滿足內存分配需求時,拋出OutOfMemoryError異常;
  3. 方法區與永久代不等價。HotSpot虛擬機只是將GC分代收集擴展至方法區,或者說使用永久代來實現方法區而已,這樣可以省去爲方法區專門編寫內存管理的代碼。HotSpot虛擬機也在放棄永久代並逐步改爲採用Native Memory來實現方法區,而對於其他虛擬機來說則不存在永久代概念;
  4. 屬於線程共享

運行時常量池

  1. 方法區的一部分;
  2. class文件除了包含類的版本、字段、方法、接口等描述信息,還包括常量池,用於存儲編譯期生成的各種字面量和符號引用。類的常量池部分在類加載後存儲在運行時常量池中;
  3. 具備動態性。由於Java並不要求常量一定只有編譯期才能產生,也就是並非預置入class文件中常量池的內容才能進入運行時常量池,運行期間也可以將新的常量放入池中,如String類的intern()方法;
  4. 無法再申請內存時,拋出OutOfMemoryError異常

直接內存

直接內存並不是虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域。但是這部分內存會被頻繁的使用,而且也可能導致OutOfMemoryError異常。在JDK1.4中新加入的NIO類,引入一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可以使用Native函數庫直接分配堆外內存,然後通過一個存儲在Java堆中的DirectByteBuffer對象作爲這塊內存的引用進行操作。這樣可以避免在Java堆和Native堆中來回複製數據,顯著提高性能。

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