Java虛擬機在運行時會把其管理的內(nèi)存劃分為若干不同的數(shù)據(jù)區(qū)域?!禞ava虛擬機規(guī)范》規(guī)定的數(shù)據(jù)區(qū)域通常包括程序計數(shù)器、Java虛擬機棧、本地方法棧、Java堆、方法區(qū)、運行時常量池以及直接內(nèi)存。這些區(qū)域都會有各自不同的生存周期以及各自不同的用途,本文主要介紹這些內(nèi)存區(qū)域以及各個內(nèi)存區(qū)域可能拋出的異常。
程序計數(shù)器
程序計數(shù)器相當于當前線程所執(zhí)行字節(jié)碼的行號指示器。字節(jié)碼解釋器通過改變這個計數(shù)器來選取下一條需要執(zhí)行的字節(jié)碼指令,程序的循環(huán)、跳轉(zhuǎn)、異常處理、線程切換都需要依賴這個計數(shù)器來完成。
一個Java虛擬機內(nèi)部可以有多個線程,每個線程都會有單獨的程序計數(shù)器,程序計數(shù)器屬于線程私有內(nèi)存,各個計數(shù)器之間互不影響。
程序計數(shù)器記錄的只能是Java方法編譯出的字節(jié)碼指令地址,對于Native方法,則計數(shù)器為空。程序計數(shù)器不會出現(xiàn)OutOfMemoryError異常。
Java虛擬機棧
Java虛擬機棧就是我們經(jīng)常說的堆棧中的棧內(nèi)存。虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每個方法從調(diào)用到執(zhí)行完成的過程都對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。
虛擬機棧中有一個局部變量表,局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對象引用和指向一條字節(jié)碼指令地址的returnAddress類型。
虛擬機棧是線程私有的,其生命周期與線程相同。此區(qū)域可能會出現(xiàn)兩種異常:如果請求棧深度大于虛擬機最大允許棧深度則拋出StackOverflowError異常;如果虛擬機棧在動態(tài)擴展時無法申請到足夠的內(nèi)存則會拋出OutOfMemoryError異常。
本地方法棧
同Java虛擬機棧相似,本地方法棧是Native方法執(zhí)行的內(nèi)存模型,其內(nèi)部也會拋出StackOverflowError和OutOfMemory異常。
Java 堆
Java堆用來存放虛擬機在運行時創(chuàng)建的Java對象實例。Java虛擬機規(guī)范規(guī)定:所有的對象實例以及數(shù)組都要在堆上分配。不過現(xiàn)在很多技術比如Just InTime(及時編譯),允許在棧中分配對象內(nèi)存。
Java堆內(nèi)存被所有線程共享,任何線程都可以在上面創(chuàng)建對象。內(nèi)存回收(GC)主要在Java堆上進行。
Java堆內(nèi)存也是虛擬機所管理的內(nèi)存最大的一塊。其內(nèi)存只要邏輯上連續(xù)即可,允許物理上不連續(xù)。如果在創(chuàng)建對象時堆內(nèi)存沒有足夠的內(nèi)存分配會拋出OutOfMemoryError異常。
方法區(qū)
方法區(qū)用來存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、及時編譯器編譯后的代碼等數(shù)據(jù),被所有線程共享。
當方法區(qū)無法滿足內(nèi)存分配時,會拋出OutOfMemoryError異常。
運行時常量池
運行時常量池是方法區(qū)的一部分,Class文件中的常量池(Constant Pool Table)在類加載后會放到運行時常量池中。
Class常量池用于存放編譯期生成的各種字面常量和符號引用。運行時常量池與Class常量池的區(qū)別就是動態(tài)性。運行時常量池不僅僅允許編譯期放入常量池,也允許運行時將新的常量放入常量池。而Class常量池只能在編譯期生成。Java虛擬機規(guī)范對Class常量池要求嚴格,對運行時常量池的要求則比較寬松。
當常量池無法再申請到內(nèi)存空間時會拋出OutOfMemoryError異常。
直接內(nèi)存
直接內(nèi)存也就是我們本機可用的物理內(nèi)存空間,不屬于任何Java虛擬機,但任何虛擬機都可以在上面操作。例如,Java虛擬機可以通過NIO包中提供的方法直接在物理內(nèi)存中分配。
當Java虛擬機要求分配的內(nèi)存大于本機物理內(nèi)存時就會拋出OutOfMemoryError異常。
內(nèi)存溢出與內(nèi)存泄露的區(qū)別
內(nèi)存溢出
內(nèi)存溢出是指分配對象的內(nèi)存超過虛擬機所允許的最大內(nèi)存,此時所有的對象實例均有用。優(yōu)化方案就是嘗試減少程序運行時內(nèi)存消耗。
內(nèi)存泄露
某些對象不再有用,但由于不正確的引用關系造成對象內(nèi)存無法釋放,最終導致所有對象的內(nèi)存超過虛擬機所允許的最大值。所以要檢查每個對象的生命周期,確保長生命周期對象引用短生命周期時釋放內(nèi)存。