JVM(五)---OutOfMemoryError實例

一 前言

 OutOfMemoryError異常是在編程過程中因爲程序的處理問題或者jvm參數配置的問題而導致的錯誤。在虛擬機的這幾個運行時區域都有發生OutOfMemoryError的可能:java堆,虛擬機棧,本地方法棧 ,方法區,運行時常量池,直接內存。下面針對這些例舉幾個代碼例子來說明。

二 java堆溢出

  Java堆用於存儲對象實例,只要不斷的創建對象,並且保證GC Roots到對象之間有可達的路徑來避免垃圾回收機制來清除這些對象,那麼在對象數量達到最大堆的容量限制後就會產生內存溢出異常。

下面代碼模擬:

import java.util.ArrayList;
import java.util.List;

public class HeapOom {

	public static void main(String[] args) {
		List<OomObject> list=new ArrayList<>();
		while (true) {
			list.add(new OomObject());
		}
	}
	
}
class OomObject{
	
}

然後設置其內存大小,可以縮小其內存大小,比如我設置最大內存爲10m

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/dump/a.dump

這句話是在如果堆內存溢出會在d盤的dump文件下生成dump文件。

運行後會報錯:

如何解決此問題:

可以通過內存分析工具對生成的dump文件進行,JDK提供了一些內存泄漏的分析工具,如jconsole,jvisualvm等,這裏使用MAT(Memory Analyzer Tool)工具。

MAT如何具體使用可以參考這篇博客:Java內存分析工具MAT(Memory Analyzer Tool)安裝使用實例

打開dominator_tree通過列名Shallow Heap 和 Retained Heap,Percentage獲取相信的信息。找到percentage最大也就是佔用內存最高的className,想了解Shallow Heap和 Retained Heap,可以參考此篇博客 Shallow Heap 和 Retained Heap的區別

我們找到了佔用內存最大的class,點擊拓展會發現OomObject對象佔用了絕大多數內存

點擊右鍵 選擇exclude all phantom/weak/soft etc.references,排除虛引用/弱引用/軟引用等的引用鏈,因爲被虛引用/弱引用/軟引用的對象可以直接被GC給回收,我們要看的就是某個對象否還存在Strong 引用鏈(在導出HeapDump之前要手動出發GC來保證),如果有,則說明存在內存泄漏

內存泄漏我們應該在使用的時候就應該避免,

1.好的辦法是使用臨時變量的時候,讓引用變量在推出活動域後自動設置爲null,暗示垃圾收集器來收集該對象,防止發生內存泄漏。
2.使用大對象時,在用完後賦值爲null
3.程序進行字符串處理時,儘量避免使用String,而應該使用StringBuffer。 
因爲String類是不可變的,每一個String對象都會獨立佔用內存一塊區域。
4.避免一些死循環等重複創建或對集合添加元素,撐爆內存
5.簡潔數據結構、少用靜態集合等
6.及時的關閉打開的文件,socket句柄等
7.多關注事件監聽(listeners)和回調(callbacks),比如註冊了一個listener,當它不再被使用的時候,忘了註銷該listener,可能就會產生內存泄露

三 虛擬機棧與本地方法棧溢出

Java 虛擬機棧會出現兩種異常:StackOverFlowError 和 OutOfMemoryError。

StackOverFlowError: 若Java虛擬機棧的內存大小不允許動態擴展,那麼當線程請求棧的深度超過當前Java虛擬機棧的最大深度的時候,就拋出StackOverFlowError異常。
OutOfMemoryError: 若 Java 虛擬機棧的內存大小允許動態擴展,且當線程請求棧時內存用完了,(當前大部分的Java虛擬機都是可以動態擴展的,只不過Java虛擬機規範中也允許固定長度的虛擬機棧),當擴展時無法申請到足夠的內存時會拋出OutOfMemorryError異常。
1.單線程 環境下

使用-Xss參數減少棧內存容量,或者定義大量的本地變量,增大此方法的本地變量的長度,如下

public class StackSop {

	private int stackLength=1;
	public void stackLeak(){
		stackLength++;
		stackLeak();
	}
	public static void main(String[] args) {
		StackSop oom=new StackSop();
		try {
			oom.stackLeak();
		}finally {
			System.out.println("stackLength:"+oom.stackLength);
		}
	}
}

2.如果測試不限於單線程,通過不斷的建立線程的方式來產生內存溢出異常,但是這樣產生的內存溢出異常與棧空間是否足夠大並不存在任何聯繫。原因:慚怍系統分配給每個進程的內存是有限制的。32位windows限制爲2GB減去Xmx(最大堆容量)再減去MaxPermSize(最大方法區容量),程序計數器消耗內存可以忽略不計。如果虛擬機進程本身耗費的內存不計算在內,剩下的內存就由虛擬機棧和本地方法棧“瓜分”了,每個線程分配的棧容量越大,可以建立的線程數量自然就越少,建立線程時就越容易把剩下的內存耗盡

如果是建立過多的多線程導致的內存溢出,在不能減少線程數或者更換64位虛擬機的情況下,就只能通過減少最大堆和減少棧容量來換取更多的線程。

 

 

 

 


 

 

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