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