內存溢出是指應用系統中存在沒法回收的內存或使用的內存過多,最終使得程序運行要用到的內存大於虛擬機能提供的最大內存。java
引發內存溢出的緣由有不少種,常見的有如下幾種:
1.內存中加載的數據量過於龐大,如一次從數據庫取出過多數據;
2.集合類中有對對象的引用,使用完後未清空,使得JVM不能回收;
3.代碼中存在死循環或循環產生過多重複的對象實體;
4.使用的第三方軟件中的BUG;
5.啓動參數內存值設定的太小;程序員
內存溢出的解決方案:
第一步,修改JVM啓動參數,直接增長內存。(-Xms,-Xmx參數必定不要忘記加。)web
第二步,檢查錯誤日誌,查看「OutOfMemory」錯誤前是否有其它異常或錯誤。算法
第三步,對代碼進行走查和分析,找出可能發生內存溢出的位置。數據庫
重點排查如下幾點:
1.檢查對數據庫查詢中,是否有一次得到所有數據的查詢。通常來講,若是一次取十萬條記錄到內存,就可能引發內存溢出。這個問題比較隱蔽,在上線前,數據庫中數據較少,不容易出問題,上線後,數據庫中數據多了,一次查詢就有可能引發內存溢出。所以對於數據庫查詢儘可能採用分頁的方式查詢。
2.檢查代碼中是否有死循環或遞歸調用。 編程
3.檢查是否有大循環重複產生新對象實體。 windows
4.檢查對數據庫查詢中,是否有一次得到所有數據的查詢。通常來講,若是一次取十萬條記錄到內存,就可能引發內存溢出。這個問題比較隱蔽,在上線前,數據庫中 數據較少,不容易出問題,上線後,數據庫中數據多了,一次查詢就有可能引發內存溢出。所以對於數據庫查詢儘可能採用分頁的方式查詢。 數組
5.檢查List、MAP等集合對象是否有使用完後,未清除的問題。List、MAP等集合對象會始終存有對對象的引用,使得這些對象不能被GC回收。tomcat
第四步,使用內存查看工具動態查看內存使用狀況服務器
從內存溢出看Java 環境中的內存結構
做爲有個java程序員,我想你們對下面出現的這幾個場景並不陌生,倍感親切,深惡痛絕,抓心撓肝,必定會回過頭來問爲何爲何爲何會這樣,嘿嘿,讓咱們看一下咱們平常在開發過程當中接觸內存溢出的異常:
Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Unknown Source) at java.util.Arrays.copyOf(Unknown Source) at java.util.ArrayList.grow(Unknown Source) at java.util.ArrayList.ensureExplicitCapacity(Unknown Source) at java.util.ArrayList.ensureCapacityInternal(Unknown Source) at java.util.ArrayList.add(Unknown Source) at oom.HeapOOM.main(HeapOOM.java:21)
Exception in thread "main" java.lang.StackOverflowError at java.nio.CharBuffer.arrayOffset(Unknown Source) at sun.nio.cs.UTF_8.updatePositions(Unknown Source) at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(Unknown Source) at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source) at java.nio.charset.CharsetEncoder.encode(Unknown Source) at sun.nio.cs.StreamEncoder.implWrite(Unknown Source) at sun.nio.cs.StreamEncoder.write(Unknown Source) at java.io.OutputStreamWriter.write(Unknown Source) at java.io.BufferedWriter.flushBuffer(Unknown Source) at java.io.PrintStream.write(Unknown Source) at java.io.PrintStream.print(Unknown Source) at java.io.PrintStream.println(Unknown Source)
java.lang.OutOfMemoryError: PermGen space
Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)
是否是有你們很熟悉的,碰見這樣的問題解決起來可能不簡單,可是若是如今讓你們寫個程序,故意讓程序出現下面的異常,估計能很快寫出來的也不是不少,這就要求開發人員對於java內存區域以及jvm規範有比較深的瞭解。
既然拋出了異常,首先咱們確定這些都是內存異常,只是內存異常中的不一樣種類,咱們就試着瞭解一下爲何會出現以上的異常,能夠看出有兩種異常情況::
OutOfMemoryError
StackOverflowError
其中OutOfMemoryError是在程序沒法申請到足夠的內存的時候拋出的異常,StackOverflowError是線程申請的棧深度大於虛擬機所容許的深度所拋出的異常。 但是從上面列出的異常內容也能夠看出在OutOfMemoryError類型的一場中也存在這不少異常的可能。這是爲何?覺得是在內存的不一樣結構中出現的錯誤,因此拋出的異常也就形形色色,說道這咱們不得不介紹一下java的內存結構,請看下圖(從網上摘的):
在運行時的內存區域有5個部分,Method Area(方法區),Java stack(java 虛擬機棧),Native MethodStack(本地方法棧),Heap(堆),Program Counter Regster(程序計數器)。從圖中看出方法區和堆用黃色標記,和其餘三個區域的不一樣點就是,方法區和堆是線程共享的,全部的運行在jvm上的程序都能訪問這兩個區域,堆,方法區和虛擬機的生命週期同樣,隨着虛擬機的啓動而存在,而棧和程序計數器是依賴用戶線程的啓動和結束而創建和銷燬。
Program Counter Regster(程序計數器):每個用戶線程對應一個程序計數器,用來指示當前線程所執行字節碼的行號。由程序計數器給文字碼解釋器提供嚇一條要執行的字節碼的的位置。根據jvm規範,在這個區域中不會拋出OutOfMemoryError的內存異常。
Java stack(java 虛擬機棧):這個區域是最容易出現內存異常的區域,每個線程對應生成一個線程棧,線程每執行一個方法的時候,都會建立一個棧幀,用來存放方法的局部變量表,操做樹棧,動態鏈接,方法入口,這和C#是不同的,在C#CLR中沒有棧幀的概念,都是在線程棧中經過壓棧和出棧的方式進行數據的保存。jvm規範對這個區域定義了兩種內存異常,OutOfMemoryError,StackOverflowError。
Native MethodStack(本地方法棧):和虛擬機棧同樣,不一樣的是處理的對象不同,虛擬機棧處理java的字節碼,而本地棧則是處理的Native方法。其餘方面一致。
Heap(堆):前面說了堆是全部線程都能訪問的,隨着虛擬機的啓動而存在,這塊區域很大,由於全部的線程都在這個區域保存實例化的對象,由於每個類型中,每一個接口實現類須要的內存不同,一個方法內的多個分支須要的內存也不盡相同,咱們只有在運行的時候才能知道要建立多少對象,須要分配多大的地址空間。GC關注的正是這樣的部份內容,因此不少時候也將堆稱爲GC堆。堆中確定不會拋出StackOverflowError類型的異常,因此只有OutOfMemoryError相關類型的異常。
Method Area(方法區):用於存放已被虛擬機加載的類信息,常量,靜態方法,即便編譯後的代碼。一樣只能拋出OutOfMemoryError相關類型的異常。
介紹完jvm內存結構中的常見區域,下面該是和咱們主題呼應的時候了,在什麼狀況下,在那個區域,如何才能復現開始提到的異常信息?從第一個開始,異常信息的內容爲:
Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Unknown Source) at java.util.Arrays.copyOf(Unknown Source) at java.util.ArrayList.grow(Unknown Source) at java.util.ArrayList.ensureExplicitCapacity(Unknown Source) at java.util.ArrayList.ensureCapacityInternal(Unknown Source) at java.util.ArrayList.add(Unknown Source) at oom.HeapOOM.main(HeapOOM.java:21)
可想而知是在堆中出現的問題,如何重現,因爲是在堆中出現這個異常,那麼就要處理好,不能被垃圾回收器給回收了,設置一下jvm中堆的最大值(這樣纔可以更快的出現錯誤),設置jvm值的方法是經過-Xms(堆的最小值),-Xmx(堆的最大值)。下面動手試一下:
package oom; import java.util.ArrayList; import java.util.List; import testbean.UserBean; /*** * * @author Think * */ public class HeapOOM { static class OOMObject { } public static void main(String[] args) { List<UserBean> users = new ArrayList<UserBean>(); while (true) { users.add(new UserBean()); } } }
UserBean對象定義以下:
package testbean; public class UserBean { String name; int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public UserBean() { super(); } }
而後在運行的時候設置jvm參數,以下:
運行一下看看結果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.util.Arrays.copyOf(Unknown Source)
at java.util.ArrayList.grow(Unknown Source)
at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
at java.util.ArrayList.add(Unknown Source)
at oom.HeapOOM.main(HeapOOM.java:21)
成功在java虛擬機堆中溢出。下面看第二個關於棧的異常,內容以下:
Exception in thread "main" java.lang.StackOverflowError at java.nio.CharBuffer.arrayOffset(Unknown Source) at sun.nio.cs.UTF_8.updatePositions(Unknown Source) at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(Unknown Source) at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source) at java.nio.charset.CharsetEncoder.encode(Unknown Source) at sun.nio.cs.StreamEncoder.implWrite(Unknown Source) at sun.nio.cs.StreamEncoder.write(Unknown Source) at java.io.OutputStreamWriter.write(Unknown Source) at java.io.BufferedWriter.flushBuffer(Unknown Source) at java.io.PrintStream.write(Unknown Source) at java.io.PrintStream.print(Unknown Source) at java.io.PrintStream.println(Unknown Source)
由於是與棧相關的話,那麼咱們在重現異常的時候就要相應的將棧內存容量設置的小一些,設置棧大小的方法是設置-Xss參數,看以下實現:
package oom; import testbean.Recursion; /*** * * * */ public class VMStackOOM { public static void main(String[] args) { Recursion recursion = new Recursion(); try { recursion.recursionself(); } catch (Throwable e) { System.out.println("current value :" + recursion.currentValue); throw e; } } }
Recursion的定義以下:
package testbean; public class Recursion { public int currentValue = 0; public void recursionself() { currentValue += 1; recursionself(); } }
運行時jvm參數的設置以下:
運行結果以下:
current value :999 Exception in thread "main" java.lang.StackOverflowError at testbean.Recursion.recursionself(Recursion.java:7) at testbean.Recursion.recursionself(Recursion.java:8) at testbean.Recursion.recursionself(Recursion.java:8) at testbean.Recursion.recursionself(Recursion.java:8) at testbean.Recursion.recursionself(Recursion.java:8) at testbean.Recursion.recursionself(Recursion.java:8)
第三個異常是關於perm的異常內容,咱們須要的是設置方法區的大小,實現方式是經過設置-XX:PermSize和-XX:MaxPermSize參數,內容以下:
java.lang.OutOfMemoryError: PermGen space
若是程序加載的類過多,例如tomcatweb容器,就會出現PermGen space異常,若是我將HeapOOM類的運行時的XX:PermSize設置爲2M,以下:
那麼程序就不會執行成功,執行的時候出現以下異常:
Error occurred during initialization of VM java.lang.OutOfMemoryError: PermGen space at sun.misc.Launcher$ExtClassLoader.getExtClassLoader(Unknown Source) at sun.misc.Launcher.<init>(Unknown Source) at sun.misc.Launcher.<clinit>(Unknown Source) at java.lang.ClassLoader.initSystemClassLoader(Unknown Source) at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)
第四個異常估計遇到的人就很少了,是DirectMemory內存相關的,內容以下:
Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)
DirectMemoruSize能夠經過設置 -XX:MaxDirectMemorySize參數指定容量大小,若是不指定的話,那麼就跟堆的最大值一致,下面是代碼實現:
package oom; import java.lang.reflect.Field; import sun.misc.Unsafe; /*** * * @author Think * */ public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true) { unsafe.allocateMemory(_1MB); } } }
運行時設置的jvm參數以下:
很容易就複線了異常信息:
Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)
關於JAVA中內存溢出的解決辦法
J2ee應用系統是運行在J2EE應用服務器上的,而j2ee應用服務器又是運行在JVM上的,
生成環境中JVM參數的優化和設置對於J2EE應用系統性能有着決定性的做用。要優化系統,則須要對JVM參數進行合理的設置,因此咱們須要瞭解究竟在什麼地方進行設置、有哪些參數以及各參數的意義分別是什麼,而且咱們還得了解JVM的內存管理機制到底是個什麼玩意兒?其實咱們在網上搜索引擎上,一搜就有能夠獲取到一大把相關信息,關鍵是咱們如何深刻的理解它們。那麼下面咱們就簡單的介紹一下究竟什麼是JVM的內存管理機制吧~!
JVM的早期版本並無進行分區管理;這樣的後果是JVM進行垃圾回收時,不得不掃描JVM所管理的整片內存,因此蒐集垃圾是很耗費資源的事情,也是早起JAVA程序的性能低下的主要緣由。隨着JVM的發展,JVM引進了分區管理的機制。
JVM所管理的全部內存資源分爲2個大的部分。永久存儲區(Permanent Space) 和堆空間(The Heap Space)。其中對空間又分爲新生區和養老區,新生區又分爲伊甸園,倖存者0區、倖存1區。以下圖:
關於個分區的用途,你們能夠參考其餘相關文檔。本教程所要處理的問題是如何解決內存溢出的問題。接下來以tomcat服務器爲例:
咱們首先得找到內存管理所要設置的參數在哪一個文件:<CATALINA_HOME>/bin/catalina.bat。
須要添加一行代碼:
JAVA_OPTS="-Xms512m-Xmx512m -Xss1024K -XX:PermSize=256m -XX:MaxPermSize=256m"
下面分別對各參數進行介紹和解釋:
JVM 相關參數:
參數名參數說明
-server 啓用可以執行優化的編譯器, 顯著提升服務器的性能,但使用可以執行優化的編譯器時,服務器的預備時間將會較長。生產環境的服務器強烈推薦設置此參數。
-Xss 單個線程堆棧大小值;JDK5.0 之後每一個線程堆棧大小爲1M,之前每一個線程堆棧大小爲256K。在相同物理內存下,減少這個值能生成更多的線程。可是操做系統對一個進程內的線程數仍是有限制的,不能無限生成,經驗值在3000~5000左右。
-XX:+UseParNewGC 可用來設置年輕代爲併發收集【多CPU】,若是你的服務器有多個CPU,你能夠開啓此參數;開啓此參數,多個CPU 可併發進行垃圾回收,可提升垃圾回收的速度。此參數和+UseParallelGC,-XX:ParallelGCThreads搭配使用。
+UseParallelGC 選擇垃圾收集器爲並行收集器。此配置僅對年輕代有效。即上述配置下,年輕代使用併發收集,而年老代仍舊使用串行收集。可提升系統的吞吐量。
-XX:ParallelGCThreads 年輕代並行垃圾收集的前提下(對併發也有效果)的線程數,增長並行度,即:同時多少個線程一塊兒進行垃圾回收。此值最好配置與處理器數目相等。永久存儲區相關參數:參數名參數說明
-Xnoclassgc 每次永久存儲區滿了後通常GC 算法在作擴展分配內存前都會觸發一次FULL GC,除非設置了-Xnoclassgc.
-XX:PermSize 應用服務器啓動時,永久存儲區的初始內存大
-XX:MaxPermSize 應用運行中,永久存儲區的極限值。爲了避免消耗擴大JVM 永久存儲區分配的開銷,將此參數和-XX:PermSize這個兩個值設爲相等。堆空間相關參數參數名參數說明
-Xms 啓動應用時,JVM 堆空間的初始大小值。
-Xmx 應用運行中,JVM 堆空間的極限值。爲了避免消耗擴大JVM 堆控件分配的開銷,將此參數和-Xms 這個兩個值設爲相等,考慮到須要開線程,講此值設置爲總內存的80%.
-Xmn 此參數硬性規定堆空間的新生代空間大小,推薦設爲堆空間大小的1/4。
上面所列的JVM 參數關係到系統的性能,而其中-XX:PermSize,
-XX:MaxPermSize,-Xms,-Xmx 和-Xmn 這5 個參數更是直接關係到系統的性能,系統是否會出現內存溢出。
-XX:PermSize 和-XX:MaxPermSize 分別設置應用服務器啓動時,永久存儲區的初始大小和極限大小;在生成環境中強烈推薦將這個兩個值設置爲相同的值,以免分配永久存儲區的開銷,具體的值可取系統「疲勞測試」獲取到的永久存儲區的極限值;若是不進行設置-XX:MaxPermSize 默認值爲64M,通常來講系統的類定義文件大小都會超過這個默認值。
-Xms 和-Xmx 分別是服務器啓動時,堆空間的初始大小和極限值。-Xms的默認值是物理內存的1/64 但小於1G,-Xmx 的默認值是物理內存的1/4 但小於1G.在生產環境中這些默認值是確定不能知足咱們的須要的。也就是你的服務器有8g 的內存,不對JVM 參數進行設置優化,應用服務器啓動時仍是按默認值來分配和約束JVM 對內存資源的使用,不會充分的利用全部的內存資源。
結論:「永久存儲區溢出(java.lang.OutOfMemoryError:Java Permanent Space)」乃是永久存儲區設置過小,不能知足系統須要的大小,此時只須要調整-XX:PermSize 和-XX:MaxPermSize 這兩個參數便可。「JVM 堆空間溢出(java.lang.OutOfMemoryError: Java heap space)」錯誤是JVM 堆空間不足,此時只須要調整-Xms 和-Xmx 這兩個參數便可。
到此咱們知道了,當系統出現內存溢出時,是哪些參數設置不合理須要調整。但咱們怎麼知道服務器啓動時,到底JVM 內存相關參數的值是多少呢?
這個問題其實Sun公司早已經意料到了,因此給咱們開發了內存使用監控工具jvmstat.
你們能夠到ORACLE官網進行下載。用它能夠很方便的看到咱們的服務器內存使用狀況。
將下載的jvmstat包解壓到如「C:\ProgramFiles\Java\jvmstat」(這是我本地java路徑,你們能夠根據本身所安裝的java環境的路徑進行解壓)。啓動完以後咱們就可使用visualgc命令了,cmd進入命令符窗口,輸入tasklist(windows下查看進程任務PID)查找到你要檢測進程PID.而後直接輸入visuglgc PID 就會彈出三個可見視圖。
以下圖:
內存溢出與數據庫鎖表的問題,能夠說是開發人員的噩夢,通常的程序異常,老是能夠知道在何時或是在什麼操做步驟上出現了異常,並且根據堆棧信息也很容易定位到程序中是某處出現了問題。內存溢出與鎖表則否則,通常現象是操做通常時間後系統愈來愈慢,直到死機,但並不能明確是在什麼操做上出現的,發生的時間點也沒有規律,查看日誌或查看數據庫也不能定位出問題的代碼。
更嚴重的是內存溢出與數據庫鎖表在系統開發和單元測試階段並不容易被發現,當系統正式上線通常時間後,操做的併發量上來了,數據也積累了一些,系統就容易出現內存溢出或是鎖表的現象,而此時系統又不能隨意停機或重啓,爲修正BUG帶來很大的困難。
本文以筆者開發和支持的多個項目爲例,與你們分享在開發過程當中遇到的Java內存溢出和數據庫鎖表的檢測和處理解決過程。
2.內存溢出的分析
內存溢出是指應用系統中存在沒法回收的內存或使用的內存過多,最終使得程序運行要用到的內存大於虛擬機能提供的最大內存。爲了解決Java中內存溢出問題,咱們首先必須瞭解Java是如何管理內存的。Java的內存管理就是對象的分配和釋放問題。在Java中,內存的分配是由程序完成的,而內存的釋放是由垃圾收集器(Garbage Collection,GC)完成的,程序員不須要經過調用GC函數來釋放內存,由於不一樣的JVM實現者可能使用不一樣的算法管理GC,有的是內存使用到達必定程度時,GC纔開始工做,也有定時執行的,有的是中斷式執行GC。但GC只能回收無用而且再也不被其它對象引用的那些對象所佔用的空間。Java的內存垃圾回收機制是從程序的主要運行對象開始檢查引用鏈,當遍歷一遍後發現沒有被引用的孤立對象就做爲垃圾回收。
引發內存溢出的緣由有不少種,常見的有如下幾種:
l 內存中加載的數據量過於龐大,如一次從數據庫取出過多數據;
l 集合類中有對對象的引用,使用完後未清空,使得JVM不能回收;
l 代碼中存在死循環或循環產生過多重複的對象實體;
l 使用的第三方軟件中的BUG;
l 啓動參數內存值設定的太小;
3.內存溢出的解決
內存溢出雖然很棘手,但也有相應的解決辦法,能夠按照從易到難,一步步的解決。
第一步,就是修改JVM啓動參數,直接增長內存。這一點看上去彷佛很簡單,但很容易被忽略。JVM默承認以使用的內存爲64M,Tomcat默承認以使用的內存爲128MB,對於稍複雜一點的系統就會不夠用。在某項目中,就由於啓動參數使用的默認值,常常報「OutOfMemory」錯誤。所以,-Xms,-Xmx參數必定不要忘記加。
第二步,檢查錯誤日誌,查看「OutOfMemory」錯誤前是否有其它異常或錯誤。在一個項目中,使用兩個數據庫鏈接,其中專用於發送短信的數據庫鏈接使用DBCP鏈接池管理,用戶爲不將短信發出,有意將數據庫鏈接用戶名改錯,使得日誌中有許多數據庫鏈接異常的日誌,一段時間後,就出現「OutOfMemory」錯誤。經分析,這是因爲DBCP鏈接池BUG引發的,數據庫鏈接不上後,沒有將鏈接釋放,最終使得DBCP報「OutOfMemory」錯誤。通過修改正確數據庫鏈接參數後,就沒有再出現內存溢出的錯誤。
查看日誌對於分析內存溢出是很是重要的,經過仔細查看日誌,分析內存溢出前作過哪些操做,能夠大體定位有問題的模塊。
第三步,安排有經驗的編程人員對代碼進行走查和分析,找出可能發生內存溢出的位置。重點排查如下幾點:
l 檢查代碼中是否有死循環或遞歸調用。
l 檢查是否有大循環重複產生新對象實體。
l 檢查對數據庫查詢中,是否有一次得到所有數據的查詢。通常來講,若是一次取十萬條記錄到內存,就可能引發內存溢出。這個問題比較隱蔽,在上線前,數據庫中數據較少,不容易出問題,上線後,數據庫中數據多了,一次查詢就有可能引發內存溢出。所以對於數據庫查詢儘可能採用分頁的方式查詢。
l 檢查List、MAP等集合對象是否有使用完後,未清除的問題。List、MAP等集合對象會始終存有對對象的引用,使得這些對象不能被GC回收。
第四步,使用內存查看工具動態查看內存使用狀況。某個項目上線後,每次系統啓動兩天後,就會出現內存溢出的錯誤。這種狀況通常是代碼中出現了緩慢的內存泄漏,用上面三個步驟解決不了,這就須要使用內存查看工具了。
內存查看工具備許多,比較有名的有:Optimizeit Profiler、JProbe Profiler、JinSight和Java1.5的Jconsole等。它們的基本工做原理大同小異,都是監測Java程序運行時全部對象的申請、釋放等動做,將內存管理的全部信息進行統計、分析、可視化。開發人員能夠根據這些信息判斷程序是否有內存泄漏問題。通常來講,一個正常的系統在其啓動完成後其內存的佔用量是基本穩定的,而不該該是無限制的增加的。持續地觀察系統運行時使用的內存的大小,能夠看到在內存使用監控窗口中是基本規則的鋸齒形的圖線,若是內存的大小持續地增加,則說明系統存在內存泄漏問題。經過間隔一段時間取一次內存快照,而後對內存快照中對象的使用與引用等信息進行比對與分析,能夠找出是哪一個類的對象在泄漏。
經過以上四個步驟的分析與處理,基本能處理內存溢出的問題。固然,在這些過程當中也須要至關的經驗與敏感度,須要在實際的開發與調試過程當中不斷積累。
整體上來講,產生內存溢出是因爲代碼寫的很差形成的,所以提升代碼的質量是最根本的解決辦法。有的人認爲先把功能實現,有BUG時再在測試階段進行修正,這種想法是錯誤的。正如一件產品的質量是在生產製造的過程當中決定的,而不是質量檢測時決定的,軟件的質量在設計與編碼階段就已經決定了,測試只是對軟件質量的一個驗證,由於測試不可能找出軟件中全部的BUG。
--------------------------------------------------------------------------------------------------------------------------------
緣由有不少種,好比:
1.數據量過於龐大;死循環 ;靜態變量和靜態方法過多;遞歸;沒法肯定是否被引用的對象;
2.虛擬機不回收內存(內存泄漏);
說白了就是程序運行要用到的內存大於虛擬機能提供的最大內存就發生內存溢出了。 內存溢出的問題要看業務和系統大小而定,對於某些系統可能內存溢出不常見,但某些系統仍是很常見的解決的方法,
一個是優化程序代碼,若是業務龐大,邏輯複雜,儘可能減小全局變量的引用,讓程序使用完變量的時候釋放該引用可以讓垃圾回收器回收,釋放資源。
二就是物理解決,增大物理內存,而後經過:-Xms256m -Xmx256m -XX:MaxNewSize=256m -XX:MaxPermSize=256m的修改
1、內存溢出類型
1 、 java.lang.OutOfMemoryError: PermGen space
JVM 管理兩種類型的內存,堆和非堆。堆是給開發人員用的上面說的就是,是在 JVM 啓動時建立;非堆是留給 JVM 本身用的,用來存放類的信息的。它和堆不一樣,運行期內 GC 不會釋放空間。若是 web app 用了大量的第三方 jar 或者應用有太多的 class 文件而剛好 MaxPermSize 設置較小,超出了也會致使這塊內存的佔用過多形成溢出,或者 tomcat 熱部署時侯不會清理前面加載的環境,只會將 context 更改成新部署的,非堆存的內容就會愈來愈多。
2 、 java.lang.OutOfMemoryError: Java heap space
第一種狀況是個補充,主要存在問題就是出如今這個狀況中。其默認空間 ( 即 -Xms) 是物理內存的 1/64 ,最大空間 (-Xmx) 是物理內存的 1/4 。若是內存剩餘不到 40 %, JVM 就會增大堆到 Xmx 設置的值,內存剩餘超過 70 %, JVM 就會減少堆到 Xms 設置的值。因此服務器的 Xmx 和 Xms 設置通常應該設置相同避免每次 GC 後都要調整虛擬機堆的大小。假設物理內存無限大,那麼 JVM 內存的最大值跟操做系統有關,通常 32 位機是 1.5g 到 3g 之間,而 64 位的就不會有限制了。
注意:若是 Xms 超過了 Xmx 值,或者堆最大值和非堆最大值的總和超過了物理內存或者操做系統的最大限制都會引發服務器啓動不起來。
垃圾回收 GC 的角色
JVM 調用 GC 的頻度仍是很高的,主要兩種狀況下進行垃圾回收:
當應用程序線程空閒;另外一個是 java 內存堆不足時,會不斷調用 GC ,若連續回收都解決不了內存堆不足的問題時,就會報 out of memory 錯誤。由於這個異常根據系統運行環境決定,因此沒法預期它什麼時候出現。
根據 GC 的機制,程序的運行會引發系統運行環境的變化,增長 GC 的觸發機會。
爲了不這些問題,程序的設計和編寫就應避免垃圾對象的內存佔用和 GC 的開銷。顯示調用 System.GC() 只能建議 JVM 須要在內存中對垃圾對象進行回收,但不是必須立刻回收,
一個是並不能解決內存資源耗空的局面,另外也會增長 GC 的消耗。
2、 JVM 內存區域組成
簡單的說 java中的堆和棧
java把內存分兩種:一種是棧內存,另外一種是堆內存
1。在函數中定義的基本類型變量和對象的引用變量都在函數的棧內存中分配;
2。堆內存用來存放由 new建立的對象和數組
在函數(代碼塊)中定義一個變量時, java就在棧中爲這個變量分配內存空間,當超過變量的做用域後, java會自動釋放掉爲該變量所分配的內存空間;在堆中分配的內存由 java虛擬機的自動垃圾回收器來管理
堆的優點是能夠動態分配內存大小,生存期也沒必要事先告訴編譯器,由於它是在運行時動態分配內存的。缺點就是要在運行時動態分配內存,存取速度較慢;
棧的優點是存取速度比堆要快,缺點是存在棧中的數據大小與生存期必須是肯定的無靈活 性。
java 堆分爲三個區: New 、 Old 和 Permanent
GC 有兩個線程:
新建立的對象被分配到 New 區,當該區被填滿時會被 GC 輔助線程移到 Old 區,當 Old 區也填滿了會觸發 GC 主線程遍歷堆內存裏的全部對象。 Old 區的大小等於 Xmx 減去 -Xmn
java棧存放
棧調整:參數有 +UseDefaultStackSize -Xss256K,表示每一個線程可申請 256k的棧空間
每一個線程都有他本身的 Stack
3、 JVM如何設置虛擬內存
提示:在 JVM中若是 98%的時間是用於 GC且可用的 Heap size 不足 2%的時候將拋出此異常信息。
提示: Heap Size 最大不要超過可用物理內存的 80%,通常的要將 -Xms和 -Xmx選項設置爲相同,而 -Xmn爲 1/4的 -Xmx值。
提示: JVM初始分配的內存由 -Xms指定,默認是物理內存的 1/64; JVM最大分配的內存由 -Xmx指定,默認是物理內存的 1/4。
默認空餘堆內存小於 40%時, JVM就會增大堆直到 -Xmx的最大限制;空餘堆內存大於 70%時, JVM會減小堆直到 -Xms的最小限制。所以服務器通常設置 -Xms、 -Xmx相等以免在每次 GC 後調整堆的大小。
提示:假設物理內存無限大的話, JVM內存的最大值跟操做系統有很大的關係。
簡單的說就 32位處理器雖然可控內存空間有 4GB,可是具體的操做系統會給一個限制,
這個限制通常是 2GB-3GB(通常來講 Windows系統下爲 1.5G-2G, Linux系統下爲 2G-3G), 而 64bit以上的處理器就不會有限制了
提示:注意:若是 Xms超過了 Xmx值,或者堆最大值和非堆最大值的總和超過了物理內 存或者操做系統的最大限制都會引發服務器啓動不起來。
提示:設置 NewSize、 MaxNewSize相等, 「new」的大小最好不要大於 「old」的一半,緣由是 old區若是不夠大會頻繁的觸發 「主 」 GC ,大大下降了性能
JVM使用 -XX:PermSize設置非堆內存初始值,默認是物理內存的 1/64;
由 XX:MaxPermSize設置最大非堆內存的大小,默認是物理內存的 1/4。
解決方法:手動設置 Heap size
修改 TOMCAT_HOME/bin/catalina.bat
在「 echo 「Using CATALINA_BASE: $CATALINA_BASE」」上面加入如下行:
4、性能檢查工具使用
定位內存泄漏:
JProfiler 工具主要用於檢查和跟蹤系統(限於 Java 開發的)的性能。 JProfiler 能夠經過時時的監控系統的內存使用狀況,隨時監視垃圾回收,線程運行情況等手段,從而很好的監視 JVM 運行狀況及其性能。
1. 應用服務器內存長期不合理佔用,內存常常處於高位佔用,很難回收到低位;
2. 應用服務器極爲不穩定,幾乎每兩天從新啓動一次,有時甚至天天從新啓動一次;
3. 應用服務器常常作 Full GC(Garbage Collection),並且時間很長,大約須要 30-40秒,應用服務器在作 Full GC的時候是不響應客戶的交易請求的,很是影響系統性能。
由於開發環境和產品環境會有不一樣,致使該問題發生有時會在產品環境中發生, 一般可使用工具跟蹤系統的內存使用狀況,在有些個別狀況下或許某個時刻確實 是使用了大量內存致使 out of memory,這時應繼續跟蹤看接下來是否會有降低,
若是一直居高不下這確定就由於程序的緣由致使內存泄漏。
5、不健壯代碼的特徵及解決辦法
1 、儘早釋放無用對象的引用。好的辦法是使用臨時變量的時候,讓引用變量在退出活動域後,自動設置爲 null ,暗示垃圾收集器來收集該對象,防止發生內存泄露。
對於仍然有指針指向的實例, jvm 就不會回收該資源 , 由於垃圾回收會將值爲 null 的對象做爲垃圾,提升 GC 回收機制效率;
2 、咱們的程序裏不可避免大量使用字符串處理,避免使用 String ,應大量使用 StringBuffer ,每個 String 對象都得獨立佔用內存一塊區域;
3 、儘可能少用靜態變量,由於靜態變量是全局的, GC 不會回收的;
4 、避免集中建立對象尤爲是大對象, JVM 會忽然須要大量內存,這時必然會觸發 GC 優化系統內存環境;顯示的聲明數組空間,並且申請數量還極大。
這是一個案例想定供你們警惕:
使用jspsmartUpload做文件上傳,如今運行過程當中常常出現java.outofMemoryError的錯誤,用top命令看看進程使用狀況,發現內存不足2M,花了很長時間,發現是jspsmartupload的問題。把jspsmartupload組件的源碼文件(class文件)反編譯成Java文件,如夢方醒:
變量m_totalBytes表示用戶上傳的文件的總長度,這是一個很大的數。若是用這樣大的數去聲明一個byte數組,並給數組的每一個元素分配內存空間,並且m_binArray數組不能立刻被釋放,JVM的垃圾回收確實有問題,致使的結果就是內存溢出。
jspsmartUpload爲什末要這樣做,有他的緣由,根據RFC1867的http上傳標準,獲得一個文件流,並不知道文件流的長度。設計者若是想文件的長度,只有操做servletinputstream一次才知道,由於任何流都不知道大小。只有知道文件長度了,才能夠限制用戶上傳文件的長度。爲了省去這個麻煩,jspsmartUpload設計者直接在內存中打開文件,判斷長度是否符合標準,符合就寫到服務器的硬盤。這樣產生內存溢出,這只是個人一個猜想而已。
因此編程的時候,不要在內存中申請大的空間,由於web服務器的內存有限,而且儘量的使用流操做,例如
5 、儘可能運用對象池技術以提升系統性能;生命週期長的對象擁有生命週期短的對象時容易引起內存泄漏,例如大集合對象擁有大數據量的業務對象的時候,能夠考慮分塊進行處理,而後解決一塊釋放一塊的策略。
6 、不要在常常調用的方法中建立對象,尤爲是忌諱在循環中建立對象。能夠適當的使用 hashtable , vector 建立一組對象容器,而後從容器中去取那些對象,而不用每次 new 以後又丟棄
7 、通常都是發生在開啓大型文件或跟數據庫一次拿了太多的數據,形成 Out Of Memory Error 的情況,這時就大概要計算一下數據量的最大值是多少,而且設定所需最小及最大的內存空間值。