1. 程式人生 > >jvm 記憶體溢位的多種原因及優化方法

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)
複製程式碼 複製程式碼
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;

/*** 
 * 
 * @author Think
 * 
 */
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)

下面說下OOM的常見情況:

第一類記憶體溢位,也是大家認為最多,第一反應認為是的記憶體溢位,就是堆疊溢位:

那什麼樣的情況就是堆疊溢位呢?當你看到下面的關鍵字的時候它就是堆疊溢位了:

Java.lang.OutOfMemoryError: ......Java heap space.....

也就是當你看到heap相關的時候就肯定是堆疊溢位了,此時如果程式碼沒有問題的情況下,適當調整-Xmx和-Xms是可以避免的,不過一定是程式碼沒有問題的前提,為什麼會溢位呢,要麼程式碼有問題,要麼訪問量太多並且每個訪問的時間太長或者資料太多,導致資料釋放不掉,因為垃圾回收器是要找到那些是垃圾才能回收,這裡它不會認為這些東西是垃圾,自然不會去回收了;主意這個溢位之前,可能系統會提前先報錯關鍵字為:

java.lang.OutOfMemoryError:GC over head limit exceeded

這種情況是當系統處於高頻的GC狀態,而且回收的效果依然不佳的情況,就會開始報這個錯誤,這種情況一般是產生了很多不可以被釋放的物件,有可能是引用使用不當導致,或申請大物件導致,但是java heap space的記憶體溢位有可能提前不會報這個錯誤,也就是可能記憶體就直接不夠導致,而不是高頻GC.

第二類記憶體溢位,PermGen的溢位,或者PermGen 滿了的提示,你會看到這樣的關鍵字:

關鍵資訊為:

java.lang.OutOfMemoryError: PermGen space

原因:系統的程式碼非常多或引用的第三方包非常多、或程式碼中使用了大量的常量、或通過intern注入常量、或者通過動態程式碼載入等方法,導致常量池的膨脹,雖然JDK 1.5以後可以通過設定對永久帶進行回收,但是我們希望的是這個地方是不做GC的,它夠用就行,所以一般情況下今年少做類似的操作,所以在面對這種情況常用的手段是:增加-XX:PermSize和-XX:MaxPermSize的大小。

第三類記憶體溢位:在使用ByteBuffer中的allocateDirect()的時候會用到,很多javaNIO的框架中被封裝為其他的方法

溢位關鍵字:

java.lang.OutOfMemoryError: Direct buffer memory
如果你在直接或間接使用了ByteBuffer中的allocateDirect方法的時候,而不做clear的時候就會出現類似的問題,常規的引用程式IO輸出存在一個核心態與使用者態的轉換過程,也就是對應直接記憶體與非直接記憶體,如果常規的應用程式你要將一個檔案的內容輸出到客戶端需要通過OS的直接記憶體轉換拷貝到程式的非直接記憶體(也就是heap中),然後再輸出到直接記憶體由作業系統傳送出去,而直接記憶體就是由OS和應用程式共同管理的,而非直接記憶體可以直接由應用程式自己控制的記憶體,jvm垃圾回收不會回收掉直接記憶體這部分的記憶體,所以要注意了哦。

如果經常有類似的操作,可以考慮設定引數:-XX:MaxDirectMemorySize

第四類記憶體溢位錯誤:

溢位關鍵字:

java.lang.StackOverflowError

這個引數直接說明一個內容,就是-Xss太小了,我們申請很多區域性呼叫的棧針等內容是存放在使用者當前所持有的執行緒中的,執行緒在jdk 1.4以前預設是256K,1.5以後是1M,如果報這個錯,只能說明-Xss設定得太小,當然有些廠商的JVM不是這個引數,本文僅僅針對Hotspot VM而已;不過在有必要的情況下可以對系統做一些優化,使得-Xss的值是可用的。

第五類記憶體溢位錯誤:

溢位關鍵字:

java.lang.OutOfMemoryError: unable to create new native thread

上面第四種溢位錯誤,已經說明了執行緒的記憶體空間,其實執行緒基本只佔用heap以外的記憶體區域,也就是這個錯誤說明除了heap以外的區域,無法為執行緒分配一塊記憶體區域了,這個要麼是記憶體本身就不夠,要麼heap的空間設定得太大了,導致了剩餘的記憶體已經不多了,而由於執行緒本身要佔用記憶體,所以就不夠用了,說明了原因,如何去修改,不用我多說,你懂的。

第六類記憶體溢位:

溢位關鍵字

java.lang.OutOfMemoryError: request {} byte for {}out of swap

這類錯誤一般是由於地址空間不夠而導致。

六大類常見溢位已經說明JVM中99%的溢位情況,要逃出這些溢位情況非常困難,除非一些很怪異的故障問題會發生,比如由於實體記憶體的硬體問題,導致了code cache的錯誤(在由byte code轉換為native code的過程中出現,但是概率極低),這種情況記憶體 會被直接crash掉,類似還有swap的頻繁互動在部分系統中會導致系統直接被crash掉,OS地址空間不夠的話,系統根本無法啟動,呵呵;JNI的濫用也會導致一些本地記憶體無法釋放的問題,所以儘量避開JNI;socket連線資料開啟過多的socket也會報類似:IOException: Too many open files等錯誤資訊。

相關推薦

jvm 記憶體溢位多種原因優化方法

讓我們看一下我們日常在開發過程中接觸記憶體溢位的異常:   Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space at java.util.Ar

記憶體溢位多種原因優化方法

對於JVM的記憶體寫過的文章已經有點多了,而且有點爛了,不過說那麼多大多數在解決OOM的情況,於此,本文就只闡述這個內容,攜帶一些分析和理解和部分擴充套件內容,也就是JVM宕機中的一些問題,OK,下面說下OOM的常見情況: 第一類記憶體溢位,也是大家認為最多

專案出現記憶體溢位原因解決方案

記憶體溢位是指應用系統中存在無法回收的記憶體或使用的記憶體過多,最終使得程式執行要用到的記憶體大於虛擬機器能提供的最大記憶體。引起記憶體溢位的原因有很多種,常見的有以下幾種:  1.記憶體中載入的資料量過於龐大,如一次從資料庫取出過多資料;  2.集合類中有對物件的引用,使用

HttpURLConnection上傳大檔案記憶體溢位原因解決辦法

原因: 由於HttpURLConnection預設是有快取機制的,在對檔案操作時,會將讀取的資料寫入到快取區中,並不是直接寫入到伺服器上,只有當流被關閉時,才將資料提交到伺服器上。當快取區的資料大於虛擬機器給點的記憶體時,就導致記憶體溢位。 HttpURLConnectio

jmeter記憶體溢位原因解決方法

jmeter是一個java開發的開源效能測試工具,在效能測試中可支援模擬併發壓測,但有時候當模擬併發請求較大或者指令碼執行時間較長時,壓力機會出現卡頓甚至報異常————記憶體溢位,這裡就介紹下如何解決記憶體溢位及相關的知識點。。。首先來看看我們常說的記憶體洩漏、記憶體溢位是什麼?記憶體洩露是指你的應用使用資源

網站排名下降的原因解決方法 網站排名優化下降

對於中小站長而言,大部分還是依靠搜尋引擎帶來的流量,可網站的關鍵詞排名又直接的決定了能從搜尋引擎帶來多少的流量,因此,我們誰都不希望自己辛辛苦苦做上去的關鍵詞排名下降,都希望自己網站的關鍵詞排名能不斷的上升,但實際情況又令站長們大失所望,那麼,究竟是那些原因導致

Spark任務提交 yarn-cluster模式 解決jvm記憶體溢位問題 以及簡單概述jdk7方法區和jdk8元空間

yarn-cluster 提價任務流程 1、提交方式 ./spark-submit --master yarn --deploy-mode cluster --class org.apache.spark.examples.SparkPi ../lib/spark-exampl

java記憶體溢位溢位原因與排查方法

 1、 記憶體溢位的原因是什麼?  記憶體溢位是由於沒被引用的物件(垃圾)過多造成JVM沒有及時回收,造成的記憶體溢位。如果出現這種現象可行程式碼排查: 一)是否App中的類中和引用變數過多使用了Static修飾 如public staitc Student s;在類中的屬性中使用 static修飾的

JVM學習筆記】(一)jvm初體驗-記憶體溢位問題分析解決方案

####1、開始 建立Main類和Demo類,在Main類的main方法中建立List,並向List中無限建立Demo物件,造成記憶體溢位, 並輸出記憶體溢位錯誤檔案在專案目錄下,為了使等待時間減小,設定執行堆記憶體大小。 ####2、建立Demo類 package com.ch

Tomcat中JVM記憶體溢位合理配置

Tomcat本身不能直接在計算機上執行,需要依賴於硬體基礎之上的作業系統和一個Java虛擬機器。Tomcat的記憶體溢位本質就是JVM記憶體溢位,所以在本文開始時,應該先對Java JVM有關記憶體方面的知識進行詳細介紹。 一、Java JVM記憶體介紹 JVM管理兩種

JVM 記憶體溢位追蹤調優與 記憶體溢位、棧溢位原因

出處1:http://www.iteye.com寫java程式時大家一定對一下兩條異常並不陌生: java.lang.OutOfMemoryError: Java heap space java.lang.OutOfMemoryError: PermGen space 尤其當

java 記憶體溢位溢位原因與排查方法

1、 記憶體溢位的原因是什麼? 記憶體溢位是由於沒被引用的物件(垃圾)過多造成JVM沒有及時回收,造成的記憶體溢位。如果出現這種現象可行程式碼排查: 一)是否App中的類中和引用變數過多使用了Static修飾 如public staitc Student s;在類中的屬性

JVM記憶體溢位有什麼優化、具體使用場景

一、Java記憶體回收機制               不論哪種語言的記憶體分配方式,都需要返回所分配記憶體的真實地址,也就是返回一個指標到記憶體塊的首地址。Java中物件是採用new或者反射的方法建

網站開啟速度慢的原因,排查方法優化方法(大全)

如果你的網站開啟速度過慢,而你又不知道該如何解決,那麼這篇文章是非看不可了。手把手的教你如何排查網站開啟慢的原因,以及優化方法。 排查網站開啟速度慢的原因: 1網站伺服器速度或租用空間所在伺服器速度伺服器空間速度是網站開啟速度快的硬體基礎,也是先決條件。否則即使你網站頁面設

Java heap space造成tomcat響應時間過長,原因JVM記憶體分配太小,解決方法

使用Java程式從資料庫中查詢大量的資料時出現異常:java.lang.OutOfMemoryError: Java heap space 在JVM中如果98%的時間是用於GC且可用的 Heap size 不足2%的時候將丟擲此異常資訊。 JVM堆的設定是指java程式

jvm記憶體溢位問題的定位方法

jvm記憶體溢位問題的定位方法 今天給大家帶來JVM體驗之記憶體溢位問題的定位方法。 廢話不多說直接開始: 一、Java堆溢位 測試程式碼如下: import java.util.*; public class A { public static void main(String[] args) {

【Spark 深入學習-08】說說Spark分區原理優化方法

學習 格式 讀取文件 tmc 資料 數值計算 詳解 shc 存儲介質 本節內容 ------------------ · Spark為什麽要分區 · Spark分區原則及方法 · Spark分區案例 · 參考

蓄電池短路形成原因判斷方法

蓄電池短路形成原因及判斷方法 正負極板間本來是由多孔隙絕緣物——隔板隔開的,但如果有焊渣或枝晶(鉛枝)穿透,則正負板相連,形成短路。嚴重的短路可以導致該單體電壓變為零,如果導致正負極相連的物質本身電阻較大,比如枝晶,則不會馬上使該單格電壓變為零,而是發生較快的自放電,俗稱軟短路。ups不間斷電源使用過程中,

mysql主從同步延遲原因解決方法

解決方案 數據庫 master 朋友 mysql MySQL主從延遲原因以及解決方案:談到MySQL數據庫主從同步延遲原理,得從mysql的數據庫主從復制原理說起,mysql的主從復制都是單線程的操作(mysql5.6版本之前),主庫對所有DDL和DML產生binlog,binlog是順序

在js文件中寫el表達式取不到值的原因解決方法

.ajax cnblogs 一個 雙引號 ssid null 使用 ucc name 1、javascript是客戶端執行,EL是在服務端執行,而服務端比客戶端先執行,所以取不到值 2、要想獲取"${jcDropClass.jcClass.id}"的值,可以在jsp中,用一