所有的Java開發(fā)人員可能會遇到這樣的困惑?我該為堆內(nèi)存設(shè)置多大空間呢,?OutOfMemoryError的異常到底涉及到運行時數(shù)據(jù)的哪塊區(qū)域,?該怎么解決呢?其實如果你經(jīng)常解決服務(wù)器性能問題,,那么這些問題就會變的非常常見,,了解JVM內(nèi)存也是為了服務(wù)器出現(xiàn)性能問題的時候可以快速的了解哪塊的內(nèi)存區(qū)域出現(xiàn)問題,以便于快速的解決生產(chǎn)故障,。 先看一張圖,,這張圖能很清晰的說明JVM內(nèi)存結(jié)構(gòu)布局。 JVM內(nèi)存結(jié)構(gòu)布局 JVM內(nèi)存結(jié)構(gòu)主要有三大塊:堆內(nèi)存,、方法區(qū)和棧,。 堆內(nèi)存是JVM中最大的一塊由年輕代和老年代組成,而年輕代內(nèi)存又被分成三部分,,Eden空間,、From Survivor空間、To Survivor空間,,默認情況下年輕代按照8:1:1的比例來分配,; 方法區(qū)存儲類信息、常量,、靜態(tài)變量等數(shù)據(jù),,是線程共享的區(qū)域,為與Java堆區(qū)分,,方法區(qū)還有一個別名Non-Heap(非堆),; 棧又分為java虛擬機棧和本地方法棧主要用于方法的執(zhí)行。 在通過一張圖來了解如何通過參數(shù)來控制各區(qū)域的內(nèi)存大小 內(nèi)存控制參數(shù) 控制參數(shù):
沒有直接設(shè)置老年代的參數(shù),,但是可以設(shè)置堆空間大小和新生代空間大小兩個參數(shù)來間接控制。
從更高的一個維度再次來看JVM和系統(tǒng)調(diào)用之間的關(guān)系: JVM和系統(tǒng)調(diào)用之間的關(guān)系 方法區(qū)和堆是所有線程共享的內(nèi)存區(qū)域,;而java棧,、本地方法棧和程序計數(shù)器是運行是線程私有的內(nèi)存區(qū)域。 下面我們詳細介紹每個區(qū)域的作用,。 Java堆(Heap)對于大多數(shù)應(yīng)用來說,,Java堆(Java Heap)是Java虛擬機所管理的內(nèi)存中最大的一塊,。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機啟動時創(chuàng)建,。此內(nèi)存區(qū)域的唯一目的就是存放對象實例,,幾乎所有的對象實例都在這里分配內(nèi)存。 Java堆是垃圾收集器管理的主要區(qū)域,,因此很多時候也被稱做“GC堆”,。如果從內(nèi)存回收的角度看,由于現(xiàn)在收集器基本都是采用的分代收集算法,,所以Java堆中還可以細分為:新生代和老年代,;再細致一點的有Eden空間、From Survivor空間,、To Survivor空間等,。 根據(jù)Java虛擬機規(guī)范的規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,,只要邏輯上是連續(xù)的即可,,就像我們的磁盤空間一樣。在實現(xiàn)時,,既可以實現(xiàn)成固定大小的,,也可以是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現(xiàn)的(通過-Xmx和-Xms控制),。 如果在堆中沒有內(nèi)存完成實例分配,,并且堆也無法再擴展時,將會拋出OutOfMemoryError異常,。 方法區(qū)(Method Area)方法區(qū)(Method Area)與Java堆一樣,,是各個線程共享的內(nèi)存區(qū)域,它用于存儲已被虛擬機加載的類信息,、常量,、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù),。雖然Java虛擬機規(guī)范把方法區(qū)描述為堆的一個邏輯部分,,但是它卻有一個別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開來,。 對于習慣在HotSpot虛擬機上開發(fā)和部署程序的開發(fā)者來說,,很多人愿意把方法區(qū)稱為“永久代”(Permanent Generation),本質(zhì)上兩者并不等價,,僅僅是因為HotSpot虛擬機的設(shè)計團隊選擇把GC分代收集擴展至方法區(qū),,或者說使用永久代來實現(xiàn)方法區(qū)而已。 Java虛擬機規(guī)范對這個區(qū)域的限制非常寬松,,除了和Java堆一樣不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴展外,,還可以選擇不實現(xiàn)垃圾收集。相對而言,,垃圾收集行為在這個區(qū)域是比較少出現(xiàn)的,,但并非數(shù)據(jù)進入了方法區(qū)就如永久代的名字一樣“永久”存在了。這個區(qū)域的內(nèi)存回收目標主要是針對常量池的回收和對類型的卸載,,一般來說這個區(qū)域的回收“成績”比較難以令人滿意,,尤其是類型的卸載,條件相當苛刻,,但是這部分區(qū)域的回收確實是有必要的,。 根據(jù)Java虛擬機規(guī)范的規(guī)定,當方法區(qū)無法滿足內(nèi)存分配需求時,,將拋出OutOfMemoryError異常,。 方法區(qū)有時被稱為持久代(PermGen)。 所有的對象在實例化后的整個運行周期內(nèi),,都被存放在堆內(nèi)存中,。堆內(nèi)存又被劃分成不同的部分:伊甸區(qū)(Eden),幸存者區(qū)域(Survivor Sapce),,老年代(Old Generation Space),。 方法的執(zhí)行都是伴隨著線程的。原始類型的本地變量以及引用都存放在線程棧中,。而引用關(guān)聯(lián)的對象比如String,,都存在在堆中。為了更好的理解上面這段話,,我們可以看一個例子: import java.text.SimpleDateFormat;import java.util.Date;import org.apache.log4j.Logger;public class HelloWorld { private static Logger LOGGER = Logger.getLogger(HelloWorld.class.getName()); public void sayHello(String message) { SimpleDateFormat formatter = new SimpleDateFormat('dd.MM.YYYY'); String today = formatter.format(new Date()); LOGGER.info(today + ': ' + message); }} 這段程序的數(shù)據(jù)在內(nèi)存中的存放如下: image 通過JConsole工具可以查看運行中的Java程序(比如Eclipse)的一些信息:堆內(nèi)存的分配,,線程的數(shù)量以及加載的類的個數(shù); image 程序計數(shù)器(Program Counter Register)程序計數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,,它的作用可以看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器,。在虛擬機的概念模型里(僅是概念模型,各種虛擬機可能會通過一些更高效的方式去實現(xiàn)),,字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,,分支、循環(huán),、跳轉(zhuǎn),、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成,。 由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的,,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內(nèi)核)只會執(zhí)行一條線程中的指令,。因此,,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,,每條線程都需要有一個獨立的程序計數(shù)器,各條線程之間的計數(shù)器互不影響,,獨立存儲,,我們稱這類內(nèi)存區(qū)域為“線程私有”的內(nèi)存。 如果線程正在執(zhí)行的是一個Java方法,,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址,;如果正在執(zhí)行的是Natvie方法,這個計數(shù)器值則為空(Undefined),。 此內(nèi)存區(qū)域是唯一一個在Java虛擬機規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域,。 JVM棧(JVM Stacks)與程序計數(shù)器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,,它的生命周期與線程相同,。虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧,、動態(tài)鏈接,、方法出口等信息。每一個方法被調(diào)用直至執(zhí)行完成的過程,,就對應(yīng)著一個棧幀在虛擬機棧中從入棧到出棧的過程,。 局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte,、char,、short、int,、float,、long、double),、對象引用(reference類型,,它不等同于對象本身,根據(jù)不同的虛擬機實現(xiàn),,它可能是一個指向?qū)ο笃鹗嫉刂返囊弥羔?,也可能指向一個代表對象的句柄或者其他與此對象相關(guān)的位置)和returnAddress類型(指向了一條字節(jié)碼指令的地址)。 其中64位長度的long和double類型的數(shù)據(jù)會占用2個局部變量空間(Slot),,其余的數(shù)據(jù)類型只占用1個,。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當進入一個方法時,,這個方法需要在幀中分配多大的局部變量空間是完全確定的,,在方法運行期間不會改變局部變量表的大小。 在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常狀況:如果線程請求的棧深度大于虛擬機所允許的深度,,將拋出StackOverflowError異常,;如果虛擬機棧可以動態(tài)擴展(當前大部分的Java虛擬機都可動態(tài)擴展,,只不過Java虛擬機規(guī)范中也允許固定長度的虛擬機棧),,當擴展時無法申請到足夠的內(nèi)存時會拋出OutOfMemoryError異常,。 本地方法棧(Native Method Stacks)本地方法棧(Native Method Stacks)與虛擬機棧所發(fā)揮的作用是非常相似的,,其區(qū)別不過是虛擬機棧為虛擬機執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機使用到的Native方法服務(wù),。虛擬機規(guī)范中對本地方法棧中的方法使用的語言,、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒有強制規(guī)定,因此具體的虛擬機可以自由實現(xiàn)它,。甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二為一,。與虛擬機棧一樣,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常,。 哪兒的OutOfMemoryError對內(nèi)存結(jié)構(gòu)清晰的認識同樣可以幫助理解不同OutOfMemoryErrors: Exception in thread “main”: java.lang.OutOfMemoryError: Java heap space 原因:對象不能被分配到堆內(nèi)存中 Exception in thread “main”: java.lang.OutOfMemoryError: PermGen space 原因:類或者方法不能被加載到持久代,。它可能出現(xiàn)在一個程序加載很多類的時候,比如引用了很多第三方的庫,; Exception in thread “main”: java.lang.OutOfMemoryError: Requested array size exceeds VM limit 原因:創(chuàng)建的數(shù)組大于堆內(nèi)存的空間 Exception in thread “main”: java.lang.OutOfMemoryError: request 原因:分配本地分配失敗,。JNI、本地庫或者Java虛擬機都會從本地堆中分配內(nèi)存空間,。 Exception in thread “main”: java.lang.OutOfMemoryError: reason> stack trace>(Native method) 原因:同樣是本地方法內(nèi)存分配失敗,,只不過是JNI或者本地方法或者Java虛擬機發(fā)現(xiàn) |
|
來自: 昵稱60891057 > 《Java》