Java內(nèi)存溢出實現(xiàn)原因及解決方案
1.JVM Heap(堆)溢出:java.lang.OutOfMemoryError: Java heap space
JVM在啟動的時候會自動設置JVM Heap的值, 可以利用JVM提供的-Xmn -Xms -Xmx等選項可進行設置。Heap的大小是Young Generation 和Tenured Generaion 之和。在JVM中如果98%的時間是用于GC,且可用的Heap size 不足2%的時候?qū)伋龃水惓P畔ⅰ?br /> 解決方法:手動設置JVM Heap(堆)的大小。
Java堆用于儲存對象實例。當需要為對象實例分配內(nèi)存,而堆的內(nèi)存占用又已經(jīng)達到-Xmx設置的最大值。將會拋出OutOfMemoryError異常。例子如下:
package com.demo.test; import java.util.ArrayList; import java.util.List; /** * VM Args: -Xms5m -Xmx5m */ public class HeapOOM { public static void main(String[] args) { int count = 0; List<Object> list = new ArrayList<Object>(); while(true){ list.add(new Object()); System.out.println(++count); } } }
然后在運行時設置jvm參數(shù),如下:
-Xmx為5m。其中的一次測試結(jié)果為,當count的值累加到360145時,發(fā)生如下異常:
修改-Xmx為10m。其中的一次測試結(jié)果為,當count的值累加到540217時,發(fā)生OutOfMemoryError異常。隨著-Xmx參數(shù)值的增大,java堆中可以存儲的對象也越多。
2.PermGen space溢出: java.lang.OutOfMemoryError: PermGen space
PermGen space的全稱是Permanent Generation space,是指內(nèi)存的永久保存區(qū)域。為什么會內(nèi)存溢出,這是由于這塊內(nèi)存主要是被JVM存放Class和Meta信息的,Class在被Load的時候被放入PermGen space區(qū)域,它和存放Instance的Heap區(qū)域不同,sun的 GC不會在主程序運行期對PermGen space進行清理,所以如果你的APP會載入很多CLASS的話,就很可能出現(xiàn)PermGen space溢出。一般發(fā)生在程序的啟動階段。
解決方法: 通過-XX:PermSize和-XX:MaxPermSize設置永久代大小即可。
方法區(qū)用于存放java類型的相關信息,如類名、訪問修飾符、常量池、字段描述、方法描述等。在類裝載器加載class文件到內(nèi)存的過程中,虛擬機會提取其中的類型信息,并將這些信息存儲到方法區(qū)。當需要存儲類信息而方法區(qū)的內(nèi)存占用又已經(jīng)達到-XX:MaxPermSize設置的最大值,將會拋出OutOfMemoryError異常。對于這種情況的測試,基本的思路是運行時產(chǎn)生大量的類去填滿方法區(qū),直到溢出。這里需要借助CGLib直接操作字節(jié)碼運行時,生成了大量的動態(tài)類。例子如下:
package com.demo.test; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodProxy; import net.sf.cglib.proxy.MethodInterceptor; /** * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M */ public class MethodAreaOOM { public static void main(String[] args) { int count = 0; while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(MethodAreaOOM.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invoke(obj, args); } }); enhancer.create(); System.out.println(++count); } } }
-XX:MaxPermSize為10m。其中的一次測試結(jié)果為,當count的值累加到800時,發(fā)生如下異常:
隨著-XX:MaxPermSize參數(shù)值的增大,java方法區(qū)中可以存儲的類型數(shù)據(jù)也越多。
3.棧溢出: java.lang.StackOverflowError : Thread Stack space
棧溢出了,JVM依然是采用棧式的虛擬機,這個和C和Pascal都是一樣的。函數(shù)的調(diào)用過程都體現(xiàn)在堆棧和退棧上了。調(diào)用構造函數(shù)的 “層”太多了,以致于把棧區(qū)溢出了。 通常來講,一般棧區(qū)遠遠小于堆區(qū)的,因為函數(shù)調(diào)用過程往往不會多于上千層,而即便每個函數(shù)調(diào)用需要 1K的空間(這個大約相當于在一個C函數(shù)內(nèi)聲明了256個int類型的變量),那么棧區(qū)也不過是需要1MB的空間。通常棧的大小是1-2MB的。通俗一點講就是單線程的程序需要的內(nèi)存太大了。 通常遞歸也不要遞歸的層次過多,很容易溢出。
解決方法:1:修改程序。2:通過 -Xss: 來設置每個線程的Stack大小即可。
在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常狀況:StackOverflowError和OutOfMemoryError異常。
(1)StackOverflowError異常
每當java程序代碼啟動一個新線程時,Java虛擬機都會為它分配一個Java棧。Java棧以幀為單位保存線程的運行狀態(tài)。當線程調(diào)用java方法時,虛擬機壓入一個新的棧幀到該線程的java棧中。只要這個方法還沒有返回,它就一直存在。
如果線程的方法嵌套調(diào)用層次太多(如遞歸調(diào)用),隨著java棧中幀的逐漸增多,最終會由于該線程java棧中所有棧幀大小總和大于-Xss設置的值,而產(chǎn)生StackOverflowError內(nèi)存溢出異常。例子如下:
package com.demo.test; /** * VM Args: -Xss128k */ public class JavaVMStackSOF { private int count = 0; public static void main(String[] args) { new JavaVMStackSOF().method(); } public void method() { System.out.println(++count); method(); } }
-Xss為128k。其中的一次測試結(jié)果為,當count的值累加到2230時,發(fā)生如下異常:
隨著-Xss參數(shù)值的增大,可以嵌套的方法調(diào)用層次也相應增加。綜上所述,StackOverflowError異常是由于方法調(diào)用的層次太深,最終導致為某個線程分配的所有棧幀大小總和大于-Xss設置的值,從而發(fā)生StackOverflowError異常。
(2)OutOfMemoryError異常
java程序代碼啟動一個新線程時,沒有足夠的內(nèi)存空間為該線程分配java棧(一個線程java棧的大小由-Xss參數(shù)確定),jvm則拋出OutOfMemoryError異常。例子如下:
package com.demo.test; /** * VM Args: -Xss128k */ public class JavaVMStackOOM { public static void main(String[] args) { int count = 0; while (true) { Thread thread = new Thread(new Runnable() { public void run() { while (true) { try { Thread.sleep(5000); } catch (Exception e) { } } } }); thread.start(); System.out.println(++count); } } }
-Xss為128k。其中的一次測試結(jié)果為,當count的值累加到11958時,發(fā)生如下異常:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:693)
at com.demo.test.JavaVMStackOOM.main(JavaVMStackOOM.java:21)
隨著-Xss參數(shù)值的增大,java程序可以創(chuàng)建的總線程數(shù)越少。
4.所以Server容器啟動的時候我們經(jīng)常關心和設置JVM的幾個參數(shù)如下:
- -Xms:java Heap初始大小, 默認是物理內(nèi)存的1/64。
- -Xmx:java Heap最大值,不可超過物理內(nèi)存。
- -Xmn:young generation的heap大小,一般設置為Xmx的3、4分之一 。增大年輕代后,將會減小年老代大小,可以根據(jù)監(jiān)控合理設置。
- -Xss:每個線程的Stack大小,而最佳值應該是128K,默認值好像是512k。
- -XX:PermSize:設定內(nèi)存的永久保存區(qū)初始大小,缺省值為64M。
- -XX:MaxPermSize:設定內(nèi)存的永久保存區(qū)最大大小,缺省值為64M。
- -XX:SurvivorRatio:Eden區(qū)與Survivor區(qū)的大小比值,設置為8,則兩個Survivor區(qū)與一個Eden區(qū)的比值為2:8,一個Survivor區(qū)占整個年輕代的1/10。
- -XX:+UseParallelGC:F年輕代使用并發(fā)收集,而年老代仍舊使用串行收集。
- -XX:+UseParNewGC:設置年輕代為并行收集,JDK5.0以上,JVM會根據(jù)系統(tǒng)配置自行設置,所無需再設置此值。
- -XX:ParallelGCThreads:并行收集器的線程數(shù),值最好配置與處理器數(shù)目相等 同樣適用于CMS。
- -XX:+UseParallelOldGC:年老代垃圾收集方式為并行收集(Parallel Compacting)。
- -XX:MaxGCPauseMillis:每次年輕代垃圾回收的最長時間(最大暫停時間),如果無法滿足此時間,JVM會自動調(diào)整年輕代大小,以滿足此值。
- -XX:+ScavengeBeforeFullGC:Full GC前調(diào)用YGC,默認是true。
實例如:JAVA_OPTS=”-Xms4g -Xmx4g -Xmn1024m -XX:PermSize=320M -XX:MaxPermSize=320m -XX:SurvivorRatio=6″
第一種OutOfMemoryError: PermGen space
發(fā)生這種問題的原意是程序中使用了大量的jar或class,使java虛擬機裝載類的空間不夠,與Permanent Generation space有關。解決這類問題有以下兩種辦法:
1、增加java虛擬機中的XX:PermSize和XX:MaxPermSize參數(shù)的大小,其中XX:PermSize是初始永久保存區(qū)域大 小,XX:MaxPermSize是最大永久保存區(qū)域大小。如針對tomcat6.0,在catalina.sh 或catalina.bat文件中一系列環(huán)境變量名說明結(jié)束處(大約在70行左右) 增加一行: JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m" 如果是windows服務器還可以在系統(tǒng)環(huán)境變量中設置。感覺用tomcat發(fā)布sprint+struts+hibernate架構的程序時很容易發(fā)生這種內(nèi)存溢出錯誤。使用上述方法,我成功解決了部署ssh項目的tomcat服務器經(jīng)常宕機的問題。
2、清理應用程序中web-inf/lib下的jar,如果tomcat部署了多個應用,很多應用都使用了相同的jar,可以將共同的jar移到 tomcat共同的lib下,減少類的重復加載。這種方法是網(wǎng)上部分人推薦的,我沒試過,但感覺減少不了太大的空間,最靠譜的還是第一種方法。
第二種OutOfMemoryError: Java heap space
發(fā)生這種問題的原因是java虛擬機創(chuàng)建的對象太多,在進行垃圾回收之間,虛擬機分配的到堆內(nèi)存空間已經(jīng)用滿了,與Heap space有關。解決這類問題有兩種思路:
1、檢查程序,看是否有死循環(huán)或不必要地重復創(chuàng)建大量對象。找到原因后,修改程序和算法。 我以前寫一個使用K-Means文本聚類算法對幾萬條文本記錄(每條記錄的特征向量大約10來個)進行文本聚類時,由于程序細節(jié)上有問題,就導致了 Java heap space的內(nèi)存溢出問題,后來通過修改程序得到了解決。
2、增加Java虛擬機中Xms(初始堆大?。┖蚗mx(最大堆大?。﹨?shù)的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
如何使用Java給您的圖片瘦身之Thumbnailator技術
在java日常開發(fā)中經(jīng)常遇到對圖片資源的操作需求,如壓縮、縮放、旋轉(zhuǎn),下面這篇文章主要給大家介紹了關于如何使用Java給您的圖片瘦身之Thumbnailator技術的相關資料,需要的朋友可以參考下2022-10-10