运行时错误7内存溢出_分别从运行时和GC的角度看JAVA8内存管理
運行時區域
1.程序計數器
程序計數器(Program Counter Register)是一塊較小的內存空間,它可以看作是當前線程所執行的字節碼的行號指示器。在虛擬機概念模型里(概念模型,各種虛擬機可能會通過一些更高效的方式實現),字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令:分支、跳轉、循環、異常處理、線程恢復等基礎操作都會依賴這個計數器來完成。每個線程都有獨立的程序計數器,用來在線程切換后能恢復到正確的執行位置,各條線程之間的計數器互不影響,獨立存儲。所以它是一個“線程私有”的內存區域。此內存區域是唯一一個在JVM規范中沒有規定任何OutOfMemoryError情況的區域。2.虛擬機棧
JVM棧是線程私有的內存區域。它描述的是java方法執行的內存模型,每個方法執行的同時都會創建一個棧幀(Stack Frame)用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每個方法從調用直至完成的過程,都對應著一個棧幀從入棧到出棧的過程。每當一個方法執行完成時,該棧幀就會彈出棧幀的元素作為這個方法的返回值,并且清除這個棧幀,Java棧的棧頂的棧幀就是當前正在執行的活動棧,也就是當前正在執行的方法。就像是組成動畫的一幀一幀的圖片,方法的調用過程也是由棧幀切換來產生結果。
很多開發人員會把Java內存分為堆內存(Heap)和棧內存(Stack),這種劃分的流行只能說明大多數開發人員最關注、與對象內存分配關系最密切的內存區域是這兩塊,其中所指的“堆”在后面會講到,而所指的“棧”就是JVM棧,或者說是JVM棧中的局部變量表部分。實際上Java內存區域的劃分遠比這要復雜。局部變量表存放了編譯器可知的各種基本數據類型(int、short、byte、char、double、float、long、boolean)、對象引用(reference類型,它不等同于對象本身,可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置)和returnAddress類型(指向了一跳字節碼指令的地址)。
在JVM規范中,對這個區域規定了兩種異常情況:如果線程請求的棧深度大于虛擬機允許的深度,將拋出StackOverflowError異常;如果虛擬機棧可以動態擴展,在擴展時無法申請到足夠的內存,就會拋出OutOfMemoryError異常。
3.本地方法棧
本地方法棧和虛擬機棧所發揮的作用是很相似的,它們之間的區別不過是 虛擬機棧為虛擬機執行Java方法(字節碼)服務,而本地方法棧則為虛擬機使用到的Native方法服務。Sun HotSpot 直接就把本地方法棧和虛擬機棧合二為一。本地方法棧也會拋出StackOverflowError和OutOfMemoryError異常。4.Java堆
對于大多數應用來說,Java堆(Heap)是JVM所管理的內存中最大的一塊。它是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。主要用來存放對象實例,所有的對象實例以及數組都要在堆上分配。堆是垃圾收集器管理的主要區域,也被稱為“GC堆”,從內存回收的角度來看,堆可以細分為:新生代和老年代;再細致一點可分為:Eden空間、From Survivor空間、To Survivor空間(空間分配比例是8:1:1)。如果在堆中沒有內存完成實例分配,并且堆也無法再擴展時,將拋出OutOfMemoryError異常。5.方法區
方法區(Method Area)與堆一樣,也是各個線程共享的區域,存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據,也就是用來存儲類的描述信息—元數據的。方法區是堆的一個邏輯部分,為了區分Java堆,它還有一個別名Non-Heap(非堆)。相對而言,GC對于這個區域的收集是很少出現的。當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。
在Java 7及之前版本,我們也習慣稱它為“永久代”(Permanent Generation),更確切來說,應該是“HotSpot使用永久代實現了方法區”。需要注意的是,從Java 8開始,“永久代”已經被徹底移除,使用了一個元空間(Metaspace)來代替它,后面我們會詳細講解。
6.運行時常量池
運行時常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池( Constant pool table),用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后進入運行時常量池中存放。運行時常量池相對于class文件常量池的另外一個特性是具備動態性,java語言并不要求常量一定只有編譯器才產生,也就是并非預置入class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中。
在近三個JDK版本(1.6、1.7、1.8)中, 運行時常量池(Runtime Constant Pool)的所處區域一直在不斷的變化,在JDK1.6時它是方法區的一部分;1.7又把他放到了堆內存中;1.8之后出現了元空間,它又回到了方法區。其實,這也說明了官方對“永久代”的優化從1.7就已經開始了。7.直接內存
直接內存(Direct Memory)并不是虛擬機運行時數據區的一部分,也不是JVM規范中定義的內存區域。但這部分內存也被頻繁的使用,而且也可能導致OutOfMemoryError異常出現。它在JDK中最直觀的表現就是NIO,基于通道(Channel)與緩沖區(Buffer)的I/O方式,它可以使用Native函數庫直接分配堆外內存,然后通過一個存儲在Java堆中的 DirectByteBuffer 對象作為這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在Java堆和Native堆中來回復制數據。
從GC的角度看內存管理
年輕代、老年代、方法區(老年代由元空間替換)從Java8開始,HotSpot已經完全將永久代(Permanent Generation)移除,取而代之的是一個新的區域—元空間(MetaSpace),它使用本地內存來存儲類元數據信息。也就是說,只要本地內存足夠,它不會出現像永久代中“java.lang.OutOfMemoryError: PermGen space”這種錯誤。同樣的,對永久代的設置參數 PermSize 和 MaxPermSize 也會失效。默認情況下,“元空間”的大小可以動態調整,或者使用新參數MaxMetaspaceSize 來限制本地內存分配給類元數據的大小。
為什么叫元空間?是因為這里面存儲的是類的元數據信息。
元數據(Meta Date),關于數據的數據或者叫做用來描述數據的數據或者叫做信息的信息。這些定義都很是抽象,我們可以把元數據簡單的理解成,最小的數據單位。元數據可以為數據說明其元素或屬性(名稱、大小、數據類型、等),或其結構(長度、字段、數據列),或其相關數據(位于何處、如何聯系、擁有者)。
為什么要移除永久代,用元空間取代呢?
1.靜態變量將會從永久代移除, 放入Java heap或者native memory. 其中建議JVM的實現中將類的元數據放入 native memory, 將字符串池和類的靜態變量放入Java堆中. 這樣可以加載多少類的元數據就不在由MaxPermSize控制, 而由系統的實際可用空間來控制.
2.為什么這么做呢?
- 字符串存在永久代中,容易出現性能問題和內存溢出。
- 類及方法的信息等比較難確定其大小,因此對于永久代的大小指定比較困難,太小容易出現永久代溢出,太大則容易導致老年代溢出。
- 永久代會為 GC 帶來不必要的復雜度,并且回收效率偏低。
- 減少OOM只是表因, 更深層的原因還是要合并HotSpot和JRockit的代碼, JRockit從來沒有一個叫永久代的東西, 但是運行良好, 也不需要開發運維人員設置這么一個永久代的大小.
元空間特色
- 充分利用了Java語言規范:類及相關的元數據的生命周期與類加載器的一致。
- 每個類加載器都有它的內存區域-元空間
- 只進行線性分配
- 不會單獨回收某個類(除了重定義類 RedefineClasses 或類加載失敗)
- 沒有GC掃描或壓縮
- 元空間里的對象不會被轉移
- 如果GC發現某個類加載器不再存活,會對整個元空間進行集體回收
GC
- Full GC時,指向元數據指針都不用再掃描,減少了Full GC的時間。
- 很多復雜的元數據掃描的代碼(尤其是CMS里面的那些)都刪除了。
- 元空間只有少量的指針指向Java堆。這包括:類的元數據中指向java.lang.Class實例的指針;數組類的元數據中,指向java.lang.Class集合的指針。
- 沒有元數據壓縮的開銷
- 減少了GC Root的掃描(不再掃描虛擬機里面的已加載類的目錄和其它的內部哈希表)
- G1回收器中,并發標記階段完成后就可以進行類的卸載
元空間內存分配模型
- 絕大多數的類元數據的空間都從本地內存中分配
- 用來描述類元數據的對象也被移除
- 為元數據分配了多個映射的虛擬內存空間。
- 為每個類加載器分配一個內存塊列表。
- 塊的大小取決于類加載器的類型
- Java反射的字節碼存取器(sun.reflect.DelegatingClassLoader )占用內存更小
- 空閑塊內存返還給塊內存列表
- 當元空間為空,虛擬內存空間會被回收
- 減少了內存碎片
異常
在JVM規范的描述中,除了程序計數器以外,虛擬機內存的其他幾個運行時區域都有發生OutOfMemoryError的可能。
運行時區域異常主要原因:
- 虛擬機棧和本地方法棧 StackOverflowError、OutOfMemoryError StackOverflowError:線程請求的棧深度大于虛擬機所允許的最大深度;OutOfMemoryError:虛擬機在擴展棧時無法申請足夠的內存空間 。
- 堆 OutOfMemoryError: 對象數量到達最大堆的容量,內存泄漏、內存溢出
- 方法區和運行時常量池 OutOfMemoryError: 反射,動態代理:CGLib、JSP、OSGI等
- 內存泄露(Memory Leak):程序在申請內存后,對象沒有被GC所回收,它始終占用內存,內存泄漏的堆積最終會造成內存溢出。
- 內存溢出(Memory Overflow):程序運行過程中無法申請到足夠的內存而導致的一種錯誤。內存溢出通常發生于OLD段或Perm段垃圾回收后,仍然無內存空間容納新的Java對象的情況。通常都是由于內存泄露導致堆棧內存不斷增大,從而引發內存溢出。
總結
以上是生活随笔為你收集整理的运行时错误7内存溢出_分别从运行时和GC的角度看JAVA8内存管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python123第七章_Python入
- 下一篇: 多个cuda 被单进程沾满_报名 | 提