一、JVM的內存模型:

從大的方面來講,JVM的內存模型分為兩大塊:
永久區內存( Permanent space )和堆內存(heap space)。
棧內存(stack space)一般都不歸在JVM內存模型中,因為棧內存屬於線程級別。
每個線程都有個獨立的棧內存空間。
Permanent space里存放加載的Class類級對象如class本身,method,field等等。
heap space主要存放對象實例和數組。
heap space由Old Generation和New Generation組成,Old Generation存放生命周期長久的實例對象,而新的對象實例一般放在New Generation。
New Generation還可以再分為Eden區(聖經中的伊甸園)、和Survivor區,新的對象實例總是首先放在Eden區,Survivor區作為Eden區和Old區的緩沖,可以向Old區轉移活動的對象實例。
下圖是JVM在內存空間(堆空間)中申請新對象過程的活動圖:

沒錯,我們常見的OOM(out of memory)內存溢出異常,就是堆內存空間不足以存放新對象實例時導致。
永久區內存溢出相對少見,一般是由於需要加載海量的Class數據,超過了非堆內存的容量導致。通常出現在Web應用剛剛啟動時,因此Web應用推薦使用預加載機制,方便在部署時就發現並解決該問題。
棧內存也會溢出,但是更加少見。
堆和棧:
-->Java棧是與每一個線程關聯的,JVM在創建每一個線程的時候,會分配一定的棧空間給線程。它主要用來存儲線程執行過程中的局部變量,方法的返回值,以及方法調用上下文。棧空間隨着線程的終止而釋放。StackOverflowError:如果在線程執行的過程中,棧空間不夠用,那么JVM就會拋出此異常,這種情況一般是死遞歸造成的。
-->Java堆是由所有的線程共享的一塊內存區域,堆用來保存各種JAVA對象,比如數組,線程對象等。
-->堆和棧分離的好處:面向對象的設計,當然除了面向對象的設計帶來的維護性,復用性和擴展性方面的好處外,我們看看面向對象如何巧妙的利用了堆棧分離。如果從JAVA內存模型的角度去理解面向對象的設計,我們就會發現對象它完美的表示了堆和棧,對象的數據放在堆中,而我們編寫的那些方法一般都是運行在棧中,因此面向對象的設計是一種非常完美的設計方式,它完美的統一了數據存儲和運行。
堆內存優化:
調整JVM啟動參數-Xms -Xmx -XX:newSize -XX:MaxNewSize,如調整初始堆內存和最大對內存 -Xms256M -Xmx512M。 或者調整初始New Generation的初始內存和最大內存 -XX:newSize=128M -XX:MaxNewSize=128M。
永久區內存優化:
調整PermSize參數 如 -XX:PermSize=256M -XX:MaxPermSize=512M。
棧內存優化:
調整每個線程的棧內存容量 如 -Xss2048K
最終,一個運行中的JVM所占的內存= 堆內存 + 永久區內存 + 所有線程所占的棧內存總和 。
二、垃圾回收
以下內容轉自http://blog.csdn.net/dc_726/article/details/7934101
- package com.cdai.jvm.gc;
- public class ReferenceCount {
- final static int MB = 1024 * 1024;
- byte[] size = new byte[2 * MB];
- Object ref;
- public static void main(String[] args) {
- ReferenceCount objA = new ReferenceCount();
- ReferenceCount objB = new ReferenceCount();
- objA.ref = objB;
- objB.ref = objA;
- objA = null;
- objB = null;
- System.gc();
- System.gc();
- }
- }
- package com.cdai.jvm.gc;
- public class DeadToRebirth {
- private static DeadToRebirth hook;
- @Override
- public void finalize() throws Throwable {
- super.finalize();
- DeadToRebirth.hook = this;
- }
- public static void main(String[] args) throws Exception {
- DeadToRebirth.hook = new DeadToRebirth();
- DeadToRebirth.hook = null;
- System.gc();
- Thread.sleep(500);
- if (DeadToRebirth.hook != null)
- System.out.println("Rebirth!");
- else
- System.out.println("Dead!");
- DeadToRebirth.hook = null;
- System.gc();
- Thread.sleep(500);
- if (DeadToRebirth.hook != null)
- System.out.println("Rebirth!");
- else
- System.out.println("Dead!");
- }
- }
引用博文:
The Java Memory Architecture http://blog.codecentric.de/en/2010/01/the-java-memory-architecture-1-act/
JVM內存管理總結 http://blog.csdn.net/lengyuhong/article/details/5953544
JVM內存管理-深入垃圾收集器與內存分配策略 http://www.iteye.com/topic/802638
JVM內存管理-深入Java內存區域與OOM http://www.iteye.com/topic/802573
圖解JVM內存模型 http://longdick.iteye.com/blog/473866
圖解JVM在內存中申請對象及垃圾回收流程 http://longdick.iteye.com/blog/468368
一次Java垃圾收集調優實戰 http://www.iteye.com/topic/212967
JVM參數表 http://blogs.oracle.com/watt/resource/jvm-options-list.html
JVM的內部結構如下圖:
JVM主要包括兩個子系統和兩個組件:
1. 兩個子系統分別是Class loader子系統和Execution engine(執行引擎) 子系統;
1.1 Class loader子系統的作用:根據給定的全限定名類名(如 java.lang.Object)來裝載class文件的內容到 Runtime data area中的method area(方法區域)。Java程序員可以extends java.lang.ClassLoader類來寫自己的Class loader。
1.2 Execution engine子系統的作用:執行classes中的指令。任何JVM specification實現(JDK)的核心都是Execution engine,不同的JDK例如Sun 的JDK 和IBM的JDK好壞主要就取決於他們各自實現的Execution engine的好壞。
2. 兩個組件分別是Runtime data area (運行時數據區域)組件和Native interface(本地接口)組件。
2.1 Native interface組件:與native libraries交互,是其它編程語言交互的接口。當調用native方法的時候,就進入了一個全新的並且不再受虛擬機限制的世界,所以也很容易出現JVM無法控制的native heap OutOfMemory。
2.2 Runtime Data Area組件:這就是我們常說的JVM的內存了。它主要分為五個部分——
1、Heap (堆):一個Java虛擬實例中只存在一個堆空間,Java堆是被所有線程共享的,在虛擬機啟動時創建。Java堆的唯一目的就是存放對象實例,絕大部分的對象實例都在這里分配。Java堆內還有更細致的划分:新生代、老年代,再細致一點的:eden、from survivor、to survivor,甚至更細粒度的本地線程分配緩沖(TLAB)等,無論對Java堆如何划分,目的都是為了更好的回收內存,或者更快的分配內存。
Java堆可以處於物理上不連續的內存空間,它邏輯上是連續的即可,就像我們的磁盤空間一樣。實現時可以選擇實現成固定大小的,也可以是可擴展的,不過當前所有商業的虛擬機都是按照可擴展來實現的(通過-Xmx和-Xms控制)。如果在堆中無法分配內存,並且堆也無法再擴展時,將會拋出OutOfMemoryError異常。
2、Method Area(方法區域):被裝載的class的信息存儲在Method area的內存中。當虛擬機裝載某個類型時,它使用類裝載器定位相應的class文件,然后讀入這個class文件內容並把它傳輸到虛擬機中。叫“方法區”可能認識它的人還不太多,如果叫永久代(Permanent Generation)它的粉絲也許就多了。它還有個別名叫 做Non-Heap(非堆)。
方法區中存放了每個Class的結構信息,包括常量池、字段描述、方法描述等等。Class文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量表(constant_pool table),用於存放編譯期已可知的常量,這部分內容將在類加載后進入方法區(永久代)存放。但是Java語言並不要求常量一定只有編譯期預置入Class的常量表的內容才能進入方法區常量池,運行期間也可將新內容放入常量池(最典型的String.intern()方法)。
運行時常量池是方法區的一部分,自然受到方法區內存的限制,當常量池無法在申請到內存時會拋出OutOfMemoryError異常。
3、Java Stack(java的棧):虛擬機只會直接對Java stack執行兩種操作:以幀為單位的壓棧或出棧。棧描述的是Java方法調用的內存模型:每個方法被執行的時候,都會同時創建一個幀(Frame)用於存儲本地變量表、操作棧、動態鏈接、方法出入口等信息。每一個方法的調用至完成,就意味着一個幀在VM棧中的入棧至出棧的過程。
4、Program Counter(程序計數器):每一個線程都有它自己的PC寄存器,也是該線程啟動時創建的。PC寄存器的內容總是指向下一條將被執行指令的餓地址,這里的地址可以是一個本地指針,也可以是在方法區中相對應於該方法起始指令的偏移量。
5、Native method stack(本地方法棧):保存native方法進入區域的地址.
以上五部分只有Heap 和Method Area是被所有線程的共享使用的;而Java stack, Program counter 和Native method stack是以線程為粒度的,每個線程獨自擁有自己的部分。
此外還有本機直接內存的管理(Direct Memory) -- 直接內存並不是虛擬機運行時數據區的一部分,它根本就是本機內存而不是VM直接管理的區域。
顯然本機直接內存的分配不會受到Java堆大小的限制,但是即然是內存那肯定還是要受到本機物理內存(包括SWAP區或者Windows虛擬內存)的限制的,一般服務器管理員配置JVM參數時,會根據實際內存設置-Xmx等參數信息,但經常忽略掉直接內存,使得各個內存區域總和大於物理內存限制(包括物理的和操作系統級的限制),而導致動態擴展時出現OutOfMemoryError異常。
JVM內存模型實例以及參數對應:
- Model1
- Model2
- 對照表:
| Model-1 |
Model-2 |
Exception |
JVM Options |
| Method Area |
Perm |
java.lang.OutOfMemoryError: PermGen space |
-XX:PermSize=<value> -XX:MaxPermSize=<value> |
| Heap |
Young Tenured |
java.lang.OutOfMemoryError: Java heap space … |
-Xms<size> -Xmx<size> -Xmn<size> -XX:newSize -XX:MaxNewSize -XX:NewRatio=<value> -XX:SurvivorRatio=<value> … |
| Thread-1…N |
NULL |
… |
-Xss<size> … |
*Memory Size of Runtime JVM = Heap + Perm + Sum(Thread-1...N)
