轉自:https://blog.csdn.net/tjiyu/article/details/53915869html
下面咱們詳細瞭解Java內存區域:先說明JVM規範定義的JVM運行時分配的數據區有哪些,而後分別介紹它們的特色,並指出給出一些HotSpot虛擬機實現的不一樣點和調整參數。java
如上圖, Java虛擬機規範定義了字節碼執行期間使用的各類運行時數據區,即JVM在執行Java程序的過程當中,會把它管理的內存劃分爲若干個不一樣的數據區域,包括:算法
程序計數器、java虛擬機棧、本地方法棧、java堆、方法區、運行時常量池;安全
從線程共享角度來講,能夠分爲兩類:數據結構
一、全部線程共享的數據區多線程
方法區、運行時常量池、java堆;oracle
這些數據區域是在Java虛擬機啓動時建立的,只有當Java虛擬機退出時纔會被銷燬;ide
二、線程間隔離的數據區函數
程序計數器、java虛擬機棧、本地方法棧、性能
這些數據區域是每一個線程的"私有"數據區,每一個線程都有本身的,不與其餘線程共享;
每一個線程的數據區在建立線程時建立,並在線程退出時被銷燬;
三、另外,還一種特殊的數據區
直接內存--使用Native函數庫直接分配的堆外內存;
即Java內存區域 = JVM運行時數據區 +直接內存。
上面圖片展現的是JVM規範定義的運行時數據概念模型,實際上JVM的實現可能有所差異,下面在介紹各內存數據區時會給出一些HotSpot虛擬機實現的不一樣點和調整參數。
程序計數器(Program Counter Register),簡稱PC計數器;
一、生存特色
每一個線程都須要一個獨立的PC計數器,生命週期與所屬線程相同,各線程的計數器互不影響;
二、做用
JVM字節碼解釋器經過改變這個計數器的值來選取線程的下一條執行指令;
三、存儲內容
JVM的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的;
在任意時刻,一個線程只會執行一個方法的代碼(稱爲該線程的當前方法(Current Method));
(A)、若是這個方法是Java方法,那PC計數器就保存JVM正在執行的字節碼指令的地址;
(B)、若是該方法是native的,那PC計數器的值是空(undefined);
四、內存分配特色
PC計數器佔用較小的內存空間;
容量至少應當能保存一個returnAddress類型的數據或者一個與平臺相關的本地指針的值;
五、異常狀況
惟一一個JVM規範中沒有規定會拋出OutOfMemoryError狀況的區域;
Java虛擬機棧(Java Virtual Machine Stack,JVM Stack),指常說的棧內存(Stack);
和Java堆指的堆內存(Heap),都是須要重點關注的內存區域;
一、生存特色
每一個線程都有一個私有的,生命週期與所屬線程相同;
二、做用
描述的是Java方法執行的內存模型,與傳統語言中(如C/C++)的棧相似;
在方法調用和返回中也扮演了很重要的角色;
三、存儲內容
用於保存方法的棧幀(Stack Frame);
每一個方法從調用到執行結束,對應其棧幀在JVM棧上的入棧到出棧的過程;
棧幀:
每一個方法執行時都會建立一個棧幀,隨着方法調用而建立(入棧),隨着方法結束而銷燬(出棧);
棧幀是方法運行時的基礎結構;
棧幀用於存儲局部變量表、操做數棧、動態鏈接、方法出口等信息;
(A)、局部變量表
局部變量表(Local Variables Table)是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。
這些都是在編譯期可知的數據,因此一個方法調用時,在JVM棧中分配給該方法的局部變量空間是徹底肯定的,運行中不改變;
一個方法分配局部變量表的最大容量由Class文件中該方法的Code屬性的max_locals數據項肯定;
(B)、操做數棧
操做數棧(Operand Stack)簡稱操做棧,它是一個後進先出(Last-In-First-Out,LIFO)棧;
在方法的執行過程當中,會有各類字節碼指令往操做數棧中寫入和提取內容(任意類型的值),也就是入棧/出棧操做;
在方法調用的時候,操做數棧也用來準備調用方法的參數以及接收方法返回結果;
一個方法的操做數棧長度由Class文件中該方法的Code屬性的max_stacks數據項肯定;
(C)、動態連接
每個棧幀內部都包含一個指向運行時常量池的引用,來支持當前方法的執行過程當中實現動態連接 (Dynamic Linking);
在 Class 文件裏面,描述一個方法調用了其餘方法,或者訪問其成員變量是經過符號引用(Symbolic Reference)來表示的;
動態連接的做用就是將這些符號引用所表示的方法轉換爲實際方法的直接引用(除了在類加載階段解析的一部分符號);
四、內存分配特色
由於除了棧幀的出棧和入棧以外,JVM棧歷來不被直接操做,因此棧幀能夠在堆中分配;
JVM棧所使用的內存不須要保證是連續的;
JVM規範容許JVM棧被實現成固定大小的或者是根據計算動態擴展和收縮的:
(A)、固定大小
若是JVM棧是固定大小的,則當建立新線程的棧時,能夠獨立地選擇每一個JVM棧的大小;
(B)、動態擴展或收縮
在動態擴展或收縮JVM棧的狀況下,JVM實現應該提供調節JVM棧最大和最小內存空間的手段;
兩種狀況下,JVM實現都應當提供調節JVM棧初始內存空間大小的手段;
HotSpot VM經過"-Xss"參數設置JVM棧內存空間大小;
五、異常狀況
JVM規範中對該區域,規定了兩種可能的異常情況:
(A)、StackOverflowError
若是線程請求分配的棧深度超過JVM棧容許的最大深度時,JVM將會拋出一個StackOverflowError異常;
(B)、 OutOfMemoryError
若是JVM棧能夠動態擴展,固然擴展的動做目前沒法申請到足夠的內存去完成擴展,或者在創建新的線程時沒有足夠的內存去建立對應的虛擬機棧,那JVM將會拋出一個OutOfMemoryError異常;
該區域與方法執行的JVM字節碼指令密切相關,這裏篇幅有限,之後有時間會分析方法的調用與執行過程,再來詳細介紹該區域。
本地方法棧(Native Method Stack)與 Java虛擬機棧相似;
一、與Java虛擬機棧的區別
Java虛擬機棧爲JVM執行Java方法(也就是字節碼)服務;
本地方法棧則爲Native方法(指使用Java之外的其餘語言編寫的方法)服務;
二、HotSpot VM實現方式
JVM規範中沒有規定本地方法棧中方法使用的語言、方式和數據結構,JVM能夠自由實現;
HotSpot VM直接把本地方法棧和Java虛擬機棧合併爲一個;
Java堆(Java Heap)指常說的堆內存(Heap);
一、生存特色
全部線程共享;
生命週期與JVM相同;
二、做用
爲"new"建立的實例對象提供存儲空間;
裏面存儲的這些對象實例都是經過垃圾收集器(Garbage Collector)進行自動管理,因此Java堆也稱"GC堆"(Garbage Collected Heap);
對GC堆以及GC的參數設置調整,就是JVM調優的主要內容;
三、存儲內容
用於存放幾乎全部對象實例;
(隨JIT編譯技術和逃逸分析技術發展,少許對象實例可能在棧上分配,詳見後面介紹JIT編譯的文章);
四、內存分配特色
(A)、Java堆劃分
爲更好回收內存,或更快分配內存,須要對Java堆進行劃分:
(I)、從垃圾收集器的角度來看
JVM規範沒有規定JVM如何實現垃圾收集器;
因爲不少JVM採用分代收集算法,因此Java堆還能夠細分爲:新生代、老年代和永久代;
(II)、從內存分配角度來看
爲解決分配內存線程不安全問題,須要同步處理;
Java堆可能劃分出每一個線程私有的分配緩衝區(Thread Local Allocation Buffer,TLAB),減小線程同步;
HotSpot VM經過"-XX:+/-UseTLAB"指定是否使用TLAB;
(B)、分配調整
和JVM棧同樣,Java堆所使用的物理內存不須要保證是連續的,邏輯連續便可;
JVM規範容許Java堆被實現成固定大小的或者是根據計算動態擴展和收縮的:
兩種狀況下,JVM實現都應當提供調節JJava堆初始內存空間大小的手段;
在動態擴展或收縮的狀況下,還應該提供調節最大和最小內存空間的手段;
(C)、HotSpot VM相關調整
目前主流的JVM都把Java堆實現成動態擴展的,如HotSpot VM:
(1)、初始空間大小
經過"-Xms"或"-XX:InitialHeapSize"參數指定Java堆初始空間大小;
默認爲1/64的物理內存空間;
(2)、最大空間大小
經過"-Xmx"或"-XX:MaxHeapSize"參數指定ava堆內存分配池的最大空間大小;
默認爲1/4的物理內存空間;
Parallel垃圾收集器默認的最大堆大小是當小於等於192MB物理內存時,爲物理內存的一半,不然爲物理內存的四分之一;
(3)、各年代內存的佔用空間與可用空間的比例
經過"-XX:MinHeapFreeRatio"和"-XX:MaxHeapFreeRatio"參數設置堆中各年代內存的佔用空間與可用空間的比例保持在特定範圍內;
默認:
"-XX:MinHeapFreeRatio=40":即一個年代(新生代或老年代)內存空餘小於40%時,JVM會從未分配的堆內存中分配給該年代,以保持該年代40%的空餘內存,直到分配完"-Xmx"指定的堆內存最大限制;
"-XX:MaxHeapFreeRatio=70":即一個年代(新生代或老年代)內存空餘大於70%時,JVM會縮減該年代內存,以保持該年代70%的空餘內存,直到縮減到"-Xms"指定的堆內存最小限制;
這兩個參數不適用於Parallel垃圾收集器(經過「-XX:YoungGenerationSizeIncrement」、「-XX:TenuredGenerationSizeIncrement 」能及「-XX:AdaptiveSizeDecrementScaleFactor」調節);
(4)、年輕代與老年代的大小比例
經過"-XX:NewRatio":控制年輕代與老年代的大小比例;
默認設置"-XX:NewRatio=2"表新生代和老年代之間的比例爲1:2;
換句話說,eden和survivor空間組合的年輕代大小將是總堆大小的三分之一;
(5)、年輕代空間大小
經過"-Xmn"參數指定年輕代(nursery)的堆的初始和最大大小;
或經過"-XX:NewSize"和"-XX:MaxNewSize"限制年輕代的最小大小和最大大小;
(6)、定永久代空間大小
經過"-XX:MaxPermSize(JDK7)"或"-XX:MaxMetaspaceSize(JDK8)"參數指定永久代的最大內存大小;
經過"-XX:PermSize(JDK7)"或"-XX:MetaspaceSize(JDK8)"參數指定永久代的內存閾值--超過將觸發垃圾回收;
注:JDK8中永久代已被刪除,類元數據存儲空間在本地內存中分配;
詳情請參考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html#sthref62
(D)、調整策略
關於這些參數的調整須要垃圾收集的一些知識(之後文章會介紹),先來簡單瞭解:
當使用某種並行垃圾收集器時,應該指按期望的具體行爲而不是指定堆的大小;
讓垃圾收集器自動地、動態的調整堆的大小來知足指望的行爲;
調整的通常規則:
除非你的應用程序沒法接受長時間的暫停,不然你能夠將堆調的儘量大一些;
除非你發現問題的緣由在於老年代的垃圾收集或應用程序暫停次數過多,不然你應該將堆的較大部分分給年輕代;
五、異常狀況
若是實際所需的堆超過了垃圾收集器能提供的最大容量,那Java虛擬機將會拋出一個OutOfMemoryError異常;
該部分的內存如何分配、垃圾如何收集,上面這些參數如何調整,將在之後的文章詳細說明。
方法區(Method Area)是堆的邏輯組成部分,但有一個別名"Non-Heap"(非堆)用以區分;
一、生存特色
全部線程共享;
生命週期與JVM相同;
二、做用
爲類加載器加載Class文件並解析後的類結構信息提供存儲空間;
以及提供JVM運行時常量存儲的空間;
三、存儲內容
用於存儲JVM加載的每個類的結構信息,主要包括:
(A)、運行時常量池(Runtime Constant Pool)、字段和方法數據;
(B)、構造函數、普通方法的字節碼內容以及JIT編譯後的代碼;
(C)、還包括一些在類、實例、接口初始化時用到的特殊方法;
四、內存分配特色
(A)、分配調整
和Java堆同樣,所使用的物理內存不須要保證是連續的;
或以實現成固定大小的或者是根據計算動態擴展和收縮的;
(B)、方法區的實現與垃圾回收
JVM規範規定:
雖然方法區是堆的邏輯組成部分,但不限定實現方法區的內存位置;
甚至簡單的虛擬機實現能夠選擇在這個區域不實現垃圾收集;
由於垃圾收集主要針對常量池和類型卸載,效果不佳;
但方法區實現垃圾回收是必要的,不然容易引發內存溢出問題;
(C)、HotSpot VM相關調整
(I)、在JDK7中
使用永久代(Permanent Generation)實現方法區,這樣就能夠不用專門實現方法區的內存管理,但這容易引發內存溢出問題;
有規劃放棄永久代而改用Native Memory來實現方法區;
再也不在Java堆的永久代中生成中分配字符串常量池,而是在Java堆其餘的主要部分(年輕代和老年代)中分配;
更多請參考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/enhancements-7.html
(II)、在JDK8中
永久代已被刪除,類元數據(Class Metadata)存儲空間在本地內存中分配,並用顯式管理元數據的空間:
從OS請求空間,而後分紅塊;
類加載器從它的塊中分配元數據的空間(一個塊被綁定到一個特定的類加載器);
當爲類加載器卸載類時,它的塊被回收再使用或返回到操做系統;
元數據使用由mmap分配的空間,而不是由malloc分配的空間;
經過"-XX:MaxMetaspaceSize" (JDK8)參數指定類元數據區的最大內存大小;
經過"-XX:MetaspaceSize" (JDK8)參數指定類元數據區的內存閾值--超過將觸發垃圾回收;
詳情請參考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html#sthref62
五、異常狀況
若是方法區的內存空間不能知足內存分配請求,那Java虛擬機將拋出一個OutOfMemoryError異常;
運行常量池(Runtime Constant Pool)是方法區的一部分;
一、存儲內容
是每個類或接口的常量池(Constant_Pool)的運行時表示形式;
包括了若干種不一樣的常量:
(A)、從編譯期可知的字面量和符號引用,也即Class文件結構中的常量池;
(B)、必須運行期解析後才能得到的方法或字段的直接引用;
(C)、還包括運行時可能建立的新常量(如JDK1.6中的String類intern()方法)
直接內存(Direct Memory)不是JVM運行時數據區,也不是JVM規範中定義的內存區域;
一、特色
是使用Native函數庫直接分配的堆外內存;
被頻繁使用,且容易出現OutOfMemoryError異常;
二、做用
由於避免了在Java堆中來回複製數據,能在一些場景中顯著提升性能;
三、實現方式
JDK1.4中新加入NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O方式;
它可使用Native函數庫直接分配堆外內存,而後經過一個存儲在Java椎中的DirectByteBuffer對象做爲這塊內存的引用進行操做;
四、HotSpot VM相關調整
能夠經過"-XX:MaxDirectMemorySize"參數指定直接內存最大空間;
不會受到Java堆大小的限制,即"-Xmx"參數限制的空間不包括直接內存;
這容易致使各個內存區域總和大於物理內存限制,出現OutOfMemoryError異常;
到這裏,咱們大致瞭解Java各內存區域是什麼,有些什麼特色了,但方法執行的JVM字節碼指令如何在Java虛擬棧中運做的,以及Java堆內存如何分配、垃圾如何收集,如何進行JVM調優,將在之後的文章詳細說明。
後面咱們將分別去了解:方法的調用與執行、JIT編譯--在運行時把Class文件字節碼編譯成本地機器碼的過程、以及JVM垃圾收集相關內容……