1. JVM內存結構
(1) JDK1.7的JVM內存結構
JVM內存結構主要有三大塊:堆內存、方法區(qū)和棧犯犁。
- 堆內存是JVM中最大的一塊属愤,由年輕代和老年代組成,而年輕代內存又被分成三部分酸役,Eden空間住诸、From Survivor空間、To Survivor空間涣澡,默認情況下年輕代的這3種空間年輕代按照8:1:1的比例來分配
- 方法區(qū)存儲類信息贱呐、常量、靜態(tài)變量等數(shù)據(jù)入桂,是線程共享的區(qū)域奄薇,為與Java堆區(qū)分,方法區(qū)還有一個別名Non-Heap(非堆)
- 棧又分為java虛擬機棧和本地方法棧主要用于方法的執(zhí)行
(2) JDK1.8以后的JVM內存結構
以前的方法區(qū)(或永久代)抗愁,用來存放class馁蒂,Method等元數(shù)據(jù)信息,但在JDK1.8已經沒有了蜘腌,取而代之的是MetaSpace(元空間)沫屡,元空間不在虛擬機里面,而是直接使用本地內存撮珠。
為什么要用元空間代替永久代沮脖?
(1) 類以及方法的信息比較難確定其大小,因此對于永久代的指定比較困難,太小容易導致永久代溢出勺届,太大容易導致老年代溢出绷柒。
(2) 永久代會給GC帶來不需要的復雜度,并且回收效率偏低涮因。
(3) Oracle可能會將HotSpot和Jrockit合二為一。
2. 從更高的一個維度再次來看JVM和系統(tǒng)調用之間的關系
下面我們詳細介紹每個區(qū)域的作用
(1) Java堆(Heap)
對于大多數(shù)應用來說伺绽,Java堆(Java Heap)是Java虛擬機所管理的內存中最大的一塊养泡。Java堆是被所有線程共享的一塊內存區(qū)域,在虛擬機啟動時創(chuàng)建奈应。此內存區(qū)域的唯一目的就是存放對象實例澜掩,幾乎所有的對象實例都在這里分配內存。
Java堆是垃圾收集器管理的主要區(qū)域杖挣,因此很多時候也被稱做“GC堆”肩榕。如果從內存回收的角度看,由于現(xiàn)在收集器基本都是采用的分代收集算法惩妇,所以Java堆中還可以細分為:新生代和老年代株汉;再細致一點的有Eden空間、From Survivor空間歌殃、To Survivor空間等乔妈。
根據(jù)Java虛擬機規(guī)范的規(guī)定,Java堆可以處于物理上不連續(xù)的內存空間中氓皱,只要邏輯上是連續(xù)的即可路召,就像我們的磁盤空間一樣。在實現(xiàn)時波材,既可以實現(xiàn)成固定大小的股淡,也可以是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現(xiàn)的(通過-Xmx和-Xms控制)廷区。
如果在堆中沒有內存完成實例分配唯灵,并且堆也無法再擴展時,將會拋出OutOfMemoryError異常隙轻。
(2) 方法區(qū)(Method Area)
方法區(qū)(Method Area)與Java堆一樣早敬,是各個線程共享的內存區(qū)域,它用于存儲已被虛擬機加載的類信息大脉、常量搞监、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)镰矿。雖然Java虛擬機規(guī)范把方法區(qū)描述為堆的一個邏輯部分琐驴,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區(qū)分開來。
對于習慣在HotSpot虛擬機上開發(fā)和部署程序的開發(fā)者來說绝淡,很多人愿意把方法區(qū)稱為“永久代”(Permanent Generation)宙刘,本質上兩者并不等價,僅僅是因為HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區(qū)牢酵,或者說使用永久代來實現(xiàn)方法區(qū)而已悬包。
Java虛擬機規(guī)范對這個區(qū)域的限制非常寬松,除了和Java堆一樣不需要連續(xù)的內存和可以選擇固定大小或者可擴展外馍乙,還可以選擇不實現(xiàn)垃圾收集布近。相對而言,垃圾收集行為在這個區(qū)域是比較少出現(xiàn)的丝格,但并非數(shù)據(jù)進入了方法區(qū)就如永久代的名字一樣“永久”存在了撑瞧。這個區(qū)域的內存回收目標主要是針對常量池的回收和對類型的卸載,一般來說這個區(qū)域的回收“成績”比較難以令人滿意显蝌,尤其是類型的卸載预伺,條件相當苛刻,但是這部分區(qū)域的回收確實是有必要的曼尊。
根據(jù)Java虛擬機規(guī)范的規(guī)定酬诀,當方法區(qū)無法滿足內存分配需求時,將拋出OutOfMemoryError異常骆撇。
(3) 程序計數(shù)器(Program Counter Register)
程序計數(shù)器(Program Counter Register)是一塊較小的內存空間料滥,它的作用可以看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機的概念模型里(僅是概念模型艾船,各種虛擬機可能會通過一些更高效的方式去實現(xiàn))葵腹,字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支屿岂、循環(huán)践宴、跳轉、異常處理爷怀、線程恢復等基礎功能都需要依賴這個計數(shù)器來完成阻肩。
由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的,在任何一個確定的時刻运授,一個處理器(對于多核處理器來說是一個內核)只會執(zhí)行一條線程中的指令烤惊。因此,為了線程切換后能恢復到正確的執(zhí)行位置吁朦,每條線程都需要有一個獨立的程序計數(shù)器柒室,各條線程之間的計數(shù)器互不影響,獨立存儲逗宜,我們稱這類內存區(qū)域為“線程私有”的內存雄右。
如果線程正在執(zhí)行的是一個Java方法空骚,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址;如果正在執(zhí)行的是Natvie方法擂仍,這個計數(shù)器值則為空(Undefined)囤屹。
此內存區(qū)域是唯一一個在Java虛擬機規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。
(4) JVM棧(JVM Stacks)
與程序計數(shù)器一樣逢渔,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的肋坚,它的生命周期與線程相同。虛擬機棧描述的是Java方法執(zhí)行的內存模型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表肃廓、操作棧智厌、動態(tài)鏈接、方法出口等信息亿昏。每一個方法被調用直至執(zhí)行完成的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程档礁。
局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean角钩、byte、char呻澜、short递礼、int、float羹幸、long脊髓、double)、對象引用(reference類型栅受,它不等同于對象本身将硝,根據(jù)不同的虛擬機實現(xiàn),它可能是一個指向對象起始地址的引用指針屏镊,也可能指向一個代表對象的句柄或者其他與此對象相關的位置)和returnAddress類型(指向了一條字節(jié)碼指令的地址)依疼。
其中64位長度的long和double類型的數(shù)據(jù)會占用2個局部變量空間(Slot),其余的數(shù)據(jù)類型只占用1個而芥。局部變量表所需的內存空間在編譯期間完成分配律罢,當進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的棍丐,在方法運行期間不會改變局部變量表的大小误辑。
在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常狀況:如果線程請求的棧深度大于虛擬機所允許的深度歌逢,將拋出StackOverflowError異常巾钉;如果虛擬機棧可以動態(tài)擴展(當前大部分的Java虛擬機都可動態(tài)擴展秘案,只不過Java虛擬機規(guī)范中也允許固定長度的虛擬機棧)睛琳,當擴展時無法申請到足夠的內存時會拋出OutOfMemoryError異常盒蟆。
(5) 本地方法棧(Native Method Stacks)
本地方法棧(Native Method Stacks)與虛擬機棧所發(fā)揮的作用是非常相似的,其區(qū)別不過是虛擬機棧為虛擬機執(zhí)行Java方法(也就是字節(jié)碼)服務师骗,而本地方法棧則是為虛擬機使用到的Native方法服務历等。虛擬機規(guī)范中對本地方法棧中的方法使用的語言、使用方式與數(shù)據(jù)結構并沒有強制規(guī)定辟癌,因此具體的虛擬機可以自由實現(xiàn)它寒屯。甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二為一。與虛擬機棧一樣黍少,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常寡夹。
3. 參數(shù)配置
(1) trace跟蹤參數(shù)
參數(shù) | 作用 |
---|---|
-XX:+PrintGC/-verbose:gc | 打印GC的簡要信息 |
-XX:+PrintGCDetails | 打印GC的詳細信息 |
-XX:+PrintGCDateStamps | 打印GC發(fā)生的時間 |
-Xloggc:log/gc.log | 指定GC的log位置,以文件輸出 |
-XX:+PrintHeapAtGC | 每一次GC后都打印堆信息 |
-XX:+HeapDumpOnOutOfMemoryError | 當JVM發(fā)生OOM時厂置,自動生成DUMP文件 |
-XX:HeapDumpPath=${目錄} | 生成的DUMP文件的存放位置 |
(2) 堆參數(shù)配置
參數(shù) | 作用 |
---|---|
-Xms | 初始堆大小菩掏,默認是物理內存的1/64 |
-Xmx | 最大堆大小 默認是物理內存的1/4 |
-Xmn | 年輕代的大小,默認整個堆的3/8 |
-XX:NewSize | 設置新生代最小空間大小 |
-XX:MaxNewSize | 設置新生代最大空間大小 |
-XX:PermSize | 設置永久代最小空間大 |
-XX:MaxPermSize | 設置永久代最大空間大小 |
-Xss | 設置每個線程的堆棧大小 |
-XX:NewRatio=n | 設置年老代和年輕帶的比值昵济,如3智绸,年輕代占整個年輕代年老代和的1/4 |
-XX:SurvivorRatio=n | 年輕代中Eden區(qū)與兩個Survivor區(qū)的比值,注意Survivor區(qū)有兩個访忿,如:3瞧栗,表示Eden:Survivor=3:2,一個Survivor區(qū)占整個年輕代的1/5 |
-XX:MetaspaceSize | 初始空間大小海铆,達到該值就會觸發(fā)垃圾收集進行類型卸載迹恐,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值卧斟;如果釋放了很少的空間殴边,那么在不超過MaxMetaspaceSize時,適當提高該值 |
-XX:MaxMetaspaceSize | 空間最大內存珍语,默認是沒有限制的 |
老年代空間大姓叶肌:
沒有直接設置老年代的參數(shù),但是可以設置堆空間大小和新生代空間大小兩個參數(shù)來間接控制廊酣,老年代空間大小=堆空間大小-年輕代大空間大小