JVM內存模型
JVM內存模型可以分為兩個部分,如下圖所示,堆和方法區是所有線程共有的,而虛擬機棧,本地方法棧和程序計數器則是線程私有的。

1. 堆(Heap)
堆內存是所有線程共有的,可以分為兩個部分:年輕代和老年代。下圖中的Perm代表的是永久代,但是注意永久代並不屬於堆內存中的一部分,同時jdk1.8之后永久代也將被移除。

堆是java虛擬機所管理的內存中最大的一塊內存區域,也是被各個線程共享的內存區域,該內存區域存放了對象實例及數組(但不是所有的對象實例都在堆中)。其大小通過-Xms(最小值)和-Xmx(最大值)參數設置(最大最小值都要小於1G),前者為啟動時申請的最小內存,默認為操作系統物理內存的1/64,后者為JVM可申請的最大內存,默認為物理內存的1/4,默認當空余堆內存小於40%時,JVM會增大堆內存到-Xmx指定的大小,可通過-XX:MinHeapFreeRation=來指定這個比列;當空余堆內存大於70%時,JVM會減小堆內存的大小到-Xms指定的大小,可通過XX:MaxHeapFreeRation=來指定這個比列,當然為了避免在運行時頻繁調整Heap的大小,通常-Xms與-Xmx的值設成一樣。堆內存 = 新生代+老生代+持久代。在我們垃圾回收的時候,我們往往將堆內存分成新生代和老生代(大小比例1:2),新生代中由Eden和Survivor0,Survivor1組成,三者的比例是8:1:1,新生代的回收機制采用復制算法,在Minor GC的時候,我們都留一個存活區用來存放存活的對象,真正進行的區域是Eden+其中一個存活區,當我們的對象時長超過一定年齡時(默認15,可以通過參數設置),將會把對象放入老生代,當然大的對象會直接進入老生代。老生代采用的回收算法是標記整理算法。
2. 方法區(Method Area)
方法區也稱”永久代“,它用於存儲虛擬機加載的類信息、常量、靜態變量、是各個線程共享的內存區域。默認最小值為16MB,最大值為64MB(64位JVM由於指針膨脹,默認是85M),可以通過-XX:PermSize 和 -XX:MaxPermSize 參數限制方法區的大小。它是一片連續的堆空間,永久代的垃圾收集是和老年代(old generation)捆綁在一起的,因此無論誰滿了,都會觸發永久代和老年代的垃圾收集。不過,一個明顯的問題是,當JVM加載的類信息容量超過了參數-XX:MaxPermSize設定的值時,應用將會報OOM的錯誤。參數是通過-XX:PermSize和-XX:MaxPermSize來設定的。
3.虛擬機棧(JVM Stack)
描述的是java方法執行的內存模型:每個方法被執行的時候都會創建一個”棧幀”,用於存儲局部變量表(包括參數)、操作棧、方法出口等信息。每個方法被調用到執行完的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。聲明周期與線程相同,是線程私有的。棧幀由三部分組成:局部變量區、操作數棧、幀數據區。局部變量區被組織為以一個字長為單位、從0開始計數的數組,和局部變量區一樣,操作數棧也被組織成一個以字長為單位的數組。但和前者不同的是,它不是通過索引來訪問的,而是通過入棧和出棧來訪問的,可以看作為臨時數據的存儲區域。除了局部變量區和操作數棧外,java棧幀還需要一些數據來支持常量池解析、正常方法返回以及異常派發機制。這些數據都保存在java棧幀的幀數據區中。
局部變量表: 存放了編譯器可知的各種基本數據類型、對象引用(引用指針,並非對象本身),其中64位長度的long和double類型的數據會占用2個局部變量的空間,其余數據類型只占1個。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的局部變量是完全確定的,在運行期間棧幀不會改變局部變量表的大小空間。
4.本地方法棧(Native Stack)
與虛擬機棧基本類似,區別在於虛擬機棧為虛擬機執行的java方法服務,而本地方法棧則是為Native方法服務。(棧的空間大小遠遠小於堆)
5.程序計數器(PC Register)
是最小的一塊內存區域,它的作用是當前線程所執行的字節碼的行號指示器,在虛擬機的模型里,字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、異常處理、線程恢復等基礎功能都需要依賴計數器完成。
6.直接內存
直接內存並不是虛擬機內存的一部分,也不是Java虛擬機規范中定義的內存區域。jdk1.4中新加入的NIO,引入了通道與緩沖區的IO方式,它可以調用Native方法直接分配堆外內存,這個堆外內存就是本機內存,不會影響到堆內存的大小.