java虛擬機筆記(三)內存溢出異常

3.內存溢出異常。
3.1 java堆溢出:
復現如下
java堆用來存儲對象實例,只要不斷創建對象,利用強引用避免垃圾回收清除對象,那麼隨着對象的增多,總容量觸及最大堆空間限制之後
就會拋出內存溢出異常。

通過參數 -XX:+heapDumpOnOutOfMemoryError ,可以讓虛擬機在出現內存溢出異常的時候保存堆快照 。
報錯信息是: java.lang.outofMemoryError:java heap space

解決方式就是,通過分析堆快照,先確認堆中的對象是否必要的。
如果不是必要的,說明發生了內存泄漏,有本應該回收的對象沒有被回收,根據對象類型信息和GC roots引用鏈信息,找到對象創建的位置,
進而找到內存泄漏代碼的位置。
本人經歷過,因爲開啓了mybatis的緩存,而mybatis的緩存是在事務結束之後纔會清空,在一次有事務的大數據量的查詢的方法裏面中,明明是分批查的,按道理這些對象在查詢完之後就應該回收,但是仍然發生了內存溢出,原因是緩存中存了大量的對象沒有被回收,這是內存泄漏。

如果不是內存泄漏,這些對象都是必要,那麼就要擴大堆內存空間,或者優化代碼, 節約運行期間的內存消耗。

3.2 虛擬機棧和本地方法棧溢出
hotspot虛擬機中不區分本地方法棧和虛擬機方法棧,虛擬機規範中規定的是,如果棧深度超過限制,就拋棧溢出的異常,stackoverflowError。
如果棧動態拓展,內存不足,就拋內存溢出異常,OutOfMemoryError。
但是hotspot虛擬機的方法棧不支持動態拓展, 因此不會在拓展的時候拋內存溢出異常。
除非在申請棧幀的內存時,就無法獲取足夠的內存而拋內存溢出異常。

復現如下:
在方法裏面不斷遞歸調用自己, 拋出stackoverflowError。
在方法裏面創建大量的局部變量,然後方法遞歸調用自己,在申請棧幀時 內存不足,拋出OutOfMemoryError。

如果無限創建線程,最終拋出內存溢出OutOfMemoryError,unable to create native thread,又是什麼情況?
操作系統分配給進程的內存空間是有限的,java進程擁有的內存空間,減去最大堆空間, 最大方法區空間,直接內存,虛擬機本身的進程佔用的空間
剩下的就是可以分配給方法棧的空間,很明顯,如果線程分配到的棧空間越大,可以分配的線程的數量就越少,也就越容易內存溢出。
要注意的是棧是線程私有的,因此棧空間 就是線程要佔用的主要空間。

3.3常量池和方法區溢出

3.3.1 常量池溢出
運行時常量池是方法區的一部分。
在jdk7以前,方法區是在堆上的“永久代”,jdk8以後 移動到了元空間中。
常量池在jdk7以前是在方法區,jdk7以後被放到了堆中。

String:intern()是一個本地方法,作用是如果字符串常量池中已經包含一個等於此String對象的字符串,則返回池中這個對象的引用,否則
會將這個String對象的字符串值放到常量池中,並返回此對象的引用。
因此,在jdk7之前,如果首次遇到這個字符串,字符串常量池放的是其實是一個全新的字符串,擁有新的地址,返回的不是原來的對象。
strstr.intern() 爲false
如果在jdk7以及以後,因爲字符串常量池在堆中,字符串對象也在堆中,那麼常量池中只要記錄下地址就行了,返回就是這個字符串對象本身,str
str.intern() 爲true 。

在jdk1.6以前,常量池是在永久代,可以設置-XX:PermSize和-XX:MaxPermSize限制永久代的大小,間接限制永久代的大小。

復現溢出:
無限調用String:intern方法,放入新的常量進入常量池,且需要用一個強引用(比如說List列表)將這些常量保存起來,防止被回收。
很快就會溢出了OutOfMemoryError ,PermGen space。

這是在jdk1.6以及以前,之後的就無法復現了,因爲將常量池放到了堆中,限制永久代大小或者限制元空間大小 都無法限制常量池。
最終拋出的也是OutOfMemoryError ,java heap space。

3.3.2方法區溢出

方法區主要存放的是類型的相關信息,如類名,訪問修飾符,方法描述,字段描述,常量池等。
讓這部分區域溢出,只要不斷的創建大量的類並加載,可以通過cglib 來實現。
jdk7中的報錯信息,OutOfMemoryError ,PermGen space。

在框架中,很多框架用了動態代理,也是用的cglib,如果產生大量的代理類,也需要越大的方法區空間來保證新生成的類可以載入內存,不然也會內存溢出。

jdk8以後,取消了永久代,放到了元空間(metaSpace 不受堆空間的限制)中。
-XX:MaxMetaspaceSize 設置元空間最大值,默認是-1,即不受限制,或者說只受本地內存限制。
-XX:MetaSpaceSize 指定元空間的初始值大小,達到這個值就會觸發類型卸載。
-XX:MinMetaSpaceFreeRatio 最小元空間剩餘容量百分比,也就是達到之後這個比例後,就會進行類型卸載,可減少因爲元空間不足而觸發類型卸載。
-XX:MaxMetaSpaceFreeRatio 最大元空間剩餘容量百分比。

3.3.3 堆外內存溢出(直接內存)
直接內存(direct memory)的容量大小可以通過-XX:MaxDirectMemorySize參數來指定,如果不指定,則默認與java堆最大值一致(-Xmx指定)。
復現:
通過反射調用 unsafe.allocateMemory() 向操作系統申請內存 。

這個溢出的特點就是,在heap dump文件中不會看見什麼明顯的異常情況。如果溢出 dump文件又很小,代碼又直接或者間接使用了DirectMemory
就考慮下是否是直接內存溢出了。 DirectMemory 也會拋出內存溢出異常,但是並沒有真正向操作系統申請內存,而且通過計算髮現內存不夠,就會拋出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章