1. 程式人生 > >Java JVM:記憶體溢位(棧溢位,堆溢位,持久代溢位以及 nable to create native thread)

Java JVM:記憶體溢位(棧溢位,堆溢位,持久代溢位以及 nable to create native thread)

轉載自https://github.com/pzxwhc/MineKnowContainer/issues/25

包括:
1. 棧溢位(StackOverflowError)
2. 堆溢位(OutOfMemoryError:java heap space)
3. 永久代溢位(OutOfMemoryError: PermGen space)
4. OutOfMemoryError:unable to create native thread

Java虛擬機器規範規定JVM的記憶體分為了好幾塊,比如堆,棧,程式計數器,方法區等,而Hotspot jvm的實現中,將堆記憶體分為了兩部:新生代,老年代。在堆記憶體之外,還有永久代,其中永久代實現了規範中規定的方法區,而記憶體模型中不同的部分都會出現相應的OOM錯誤,接下來我們就分開來討論一下。

棧溢位(StackOverflowError)

棧溢位丟擲StackOverflowError錯誤,出現此種情況是因為方法執行的時候棧的深度超過了虛擬機器容許的最大深度所致。出現這種情況,一般情況下是程式錯誤所致的,比如寫了一個死遞迴,就有可能造成此種情況。 下面我們通過一段程式碼來模擬一下此種情況的記憶體溢位。

import java.util.*;    
import java.lang.*;    
public class OOMTest{     
    public void stackOverFlowMethod(){    
        stackOverFlowMethod();    
    }    
    public static void main(String... args){    
        OOMTest oom = new OOMTest();    
        oom.stackOverFlowMethod();    
    }    
}    

執行上面的程式碼,會丟擲如下的異常:

Exception in thread "main" java.lang.StackOverflowError    
        at OOMTest.stackOverFlowMethod(OOMTest.java:6)    

對於棧記憶體溢位,根據《Java 虛擬機器規範》中文版:如果執行緒請求的棧容量超過棧允許的最大容量的話,Java 虛擬機器將丟擲一個StackOverflow異常;如果Java虛擬機器棧可以動態擴充套件,並且擴充套件的動作已經嘗試過,但是無法申請到足夠的記憶體去完成擴充套件,或者在新建立執行緒的時候沒有足夠的記憶體去建立對應的虛擬機器棧,那麼Java虛擬機器將丟擲一個OutOfMemory 異常。

堆溢位(OutOfMemoryError:java heap space)

堆記憶體溢位的時候,虛擬機器會丟擲java.lang.OutOfMemoryError:java heap space,出現此種情況的時候,我們需要根據記憶體溢位的時候產生的dump檔案來具體分析(需要增加-XX:+HeapDumpOnOutOfMemoryErrorjvm啟動引數)。出現此種問題的時候有可能是記憶體洩露,也有可能是記憶體溢位了。

  • 如果記憶體洩露,我們要找出洩露的物件是怎麼被GC ROOT引用起來,然後通過引用鏈來具體分析洩露的原因。
  • 如果出現了記憶體溢位問題,這往往是程式本生需要的記憶體大於了我們給虛擬機器配置的記憶體,這種情況下,我們可以採用調大-Xmx來解決這種問題。下面我們通過如下的程式碼來演示一下此種情況的溢位:
import java.util.*;    
import java.lang.*;    
public class OOMTest{    
        public static void main(String... args){    
                List<byte[]> buffer = new ArrayList<byte[]>();    
                buffer.add(new byte[10*1024*1024]);    
        }    

}    

我們通過如下的命令執行上面的程式碼:

java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTest

程式輸出如下的資訊:

[GC 1180K->366K(19456K), 0.0037311 secs]    
[Full GC 366K->330K(19456K), 0.0098740 secs]    
[Full GC 330K->292K(19456K), 0.0090244 secs]    
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space    
        at OOMTest.main(OOMTest.java:7)    

從執行結果可以看出,JVM進行了一次Minor gc和兩次的Major gc,從Major gc的輸出可以看出,gc以後old區使用率為134K,而位元組陣列為10M,加起來大於了old generation的空間,所以丟擲了異常,如果調整-Xms21M,-Xmx21M,那麼就不會觸發gc操作也不會出現異常了。

通過上面的實驗其實也從側面驗證了一個結論:當物件大於新生代剩餘記憶體的時候,將直接放入老年代,當老年代剩餘記憶體還是無法放下的時候,觸發垃圾收集,收集後還是不能放下就會丟擲記憶體溢位異常了。

持久帶溢位(OutOfMemoryError: PermGen space)

我們知道Hotspot jvm通過持久帶實現了Java虛擬機器規範中的方法區,而執行時的常量池就是儲存在方法區中的,因此持久帶溢位有可能是執行時常量池溢位,也有可能是方法區中儲存的class物件沒有被及時回收掉或者class資訊佔用的記憶體超過了我們配置。
當持久帶溢位的時候丟擲java.lang.OutOfMemoryError: PermGen space。可能在如下幾種場景下出現:

  1. 使用一些應用伺服器的熱部署的時候,我們就會遇到熱部署幾次以後發現記憶體溢位了,這種情況就是因為每次熱部署的後,原來的class沒有被解除安裝掉。
  2. 如果應用程式本身比較大,涉及的類庫比較多,但是我們分配給持久帶的記憶體(通過-XX:PermSize和-XX:MaxPermSize來設定)比較小的時候也可能出現此種問題。
  3. 一些第三方框架,比如spring,hibernate都通過位元組碼生成技術(比如CGLib)來實現一些增強的功能,這種情況可能需要更大的方法區來儲存動態生成的Class檔案。

我們知道Java中字串常量是放在常量池中的,String.intern()這個方法執行的時候,會檢查常量池中是否存和本字串相等的物件,如果存在直接返回對常量池中物件的引用,不存在的話,先把此字串加入常量池,然後再返回字串的引用。那麼我們就可以通過String.intern方法來模擬一下執行時常量區的溢位.下面我們通過如下的程式碼來模擬此種情況:

import java.util.*;    
import java.lang.*;    
public class OOMTest{    
        public static void main(String... args){    
                List<String> list = new ArrayList<String>();    
                while(true){    
                        list.add(UUID.randomUUID().toString().intern());    
                }    
        }        
}    

我們通過如下的命令執行上面程式碼:
java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest

執行後的輸入如下圖所示:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space    
        at java.lang.String.intern(Native Method)    
        at OOMTest.main(OOMTest.java:8)   

通過上面的程式碼,我們成功模擬了執行時常量池溢位的情況,從輸出中的PermGen space可以看出確實是持久帶發生了溢位,這也驗證了,我們前面說的Hotspot jvm通過持久帶來實現方法區的說法。

OutOfMemoryError:unable to create native thread

最後我們在來看看java.lang.OutOfMemoryError:unable to create natvie thread這種錯誤。 出現這種情況的時候,一般是下面兩種情況導致的:
1. 程式建立的執行緒數超過了作業系統的限制。對於Linux系統,我們可以通過ulimit -u來檢視此限制。
2. 給虛擬機器分配的記憶體過大,導致建立執行緒的時候需要的native記憶體太少。

我們都知道作業系統對每個程序的記憶體是有限制的,我們啟動Jvm,相當於啟動了一個程序,假如我們一個程序佔用了4G的記憶體,那麼通過下面的公式計算出來的剩餘記憶體就是建立執行緒棧的時候可以用的記憶體。執行緒棧總可用記憶體=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程式計數器佔用的記憶體
通過上面的公式我們可以看出,-Xmx 和 MaxPermSize的值越大,那麼留給執行緒棧可用的空間就越小,在-Xss引數配置的棧容量不變的情況下,可以建立的執行緒數也就越小。因此如果是因為這種情況導致的unable to create native thread,那麼要麼我們增大程序所佔用的總記憶體,或者減少-Xmx或者-Xss來達到建立更多執行緒的目的。

總結:

  1. 棧記憶體溢位:程式所要求的棧深度過大導致。
  2. 堆記憶體溢位: 分清 記憶體洩露還是 記憶體容量不足。洩露則看物件如何被 GC Root 引用。不足則通過 調大 -Xms,-Xmx引數。
  3. 持久帶記憶體溢位:Class物件未被釋放,Class物件佔用資訊過多,有過多的Class物件。
  4. 無法建立本地執行緒:總容量不變,堆記憶體,非堆記憶體設定過大,會導致能給執行緒的記憶體不足。

相關推薦

Java JVM記憶體溢位溢位溢位持久溢位以及 nable to create native thread

轉載自https://github.com/pzxwhc/MineKnowContainer/issues/25 包括: 1. 棧溢位(StackOverflowError) 2. 堆溢位(OutOfMemoryError:java heap space) 3. 永久代

Java JVM記憶體溢位溢位溢位持久溢位以及 nable to create native thread,

Hotspot jvm的實現中,將堆記憶體分為了兩部:新生代,老年代。在堆記憶體之外,還有永久代, 其中永久代實現了規範中規定的方法區。 棧溢位:出現此種情況是因為方法執行的時候,棧的深度超過了虛擬機器容許的最大深度所致。 死遞迴: import java.util.*;

java JVM記憶體區域執行時資料區域

JVM的記憶體形式:   (1)方法區:存放了要載入的類的資訊(名稱,修飾符等)、類中的靜態變數、類中定義為final的變數、類中Field資訊、類中的方法資訊,當開發人員通過Class物件的getName、isInterface方法來獲取資訊時候,這些資訊都來源於方

JVM記憶體溢位詳解溢位溢位持久溢位以及無法建立本地執行緒

寫在前面 記憶體溢位和記憶體洩漏的區別: 記憶體溢位 out of memory,是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現out of memory;比如申請了一個integer,但給它存了long才能存下的數,那就是記

JVM記憶體監視手段及各區域記憶體溢位解決

引言本文僅關注一些常見的虛擬機器記憶體監視手段,以及JVM執行時資料區各個部分記憶體溢位的發生和對應的解決方案,總體來說屬於概括性總結,涉及相對不是很深入,目的是讓自己和其它初學者有一個框架性、概念性的瞭解,當遇到問題時有跡可循、不至於不知所措。一、虛擬機器記憶體監視手段虛擬

JVM快速調優之一記憶體結構記憶體和非記憶體

圖為Java虛擬機器執行時的資料區: 1.方法區 也稱"永久代” 、“非堆”, 它用於儲存虛擬機器載入的類資訊、常量、靜態變數、是各個執行緒共享的記憶體區域。預設最小值為16MB,最大值為64MB(未驗證),可以通過-XX:PermSize 和 -XX:Ma

Java記憶體模型(JVM記憶體劃分)不看後悔一看必懂

執行緒共享區:堆和方法區 執行緒獨佔區:棧,本地方法區和程式計數器 堆:存放的是new出來的東西(物件例項),被final修飾的區域性變數.java堆是垃圾收集器管理的主要區域,因此很多時候被稱為”

性能測試三十四jvm內存結構、永久

內容 清理 配置 線程 通過 棧內存 所有 不足 會有 Java內存管理機制 Java采用了自動管理內存的方式Java程序是運行在Jvm之中的Java的跨平臺的基於Jvm的跨平臺特性內存的分配和對象的創建是在Jvm中用戶可以通過一系列參數來配置Jvm Jvm運行

java記憶體分配的區別和聯絡

Java 把記憶體劃分成兩種:一種是棧記憶體,另一種是堆記憶體。在函式中定義的一些基本型別的變數和物件的引用變數都是在函式的棧記憶體中分配,當在一段程式碼塊定義一個變數時,Java 就在棧中為這個變數分配記憶體空間,當超過變數的作用域後,Java 會自動釋放掉為該變數分配的記

緩衝區溢位溢位

前言 在現在的網路攻擊中,緩衝區溢位方式的攻擊佔據了很大一部分,緩衝區溢位是一種非常普遍的漏洞,但同時,它也是非常危險的一種漏洞,輕則導致系統宕機,重則可導致攻擊者獲取系統許可權,進而盜取資料,為所欲為。 其實緩衝區攻擊說來也簡單,請看下面一段程式碼: void main(int argc, char *ar

Java練習用IF進行數字排序

新手學習import java.util.Scanner; /** Created by Administrator on 2018/4/19 0019.//*Compare.java 比較輸入值得大小並輸出 v.1*/public class Compare {public static void

Linux 程序通訊之記憶體共享Shared Memory

一、簡介 共享記憶體允許兩個程序訪問同一塊記憶體區域,它們使用同一個 key 值標記。 二、特點 優點: 通訊方便,兩個程序也是直接訪問同一塊記憶體區域,減少了資料複製的操作,速度上也有明顯優勢。 缺點: 沒有提供同步機制,往往需要我們使用其它(例如訊號)等手段實

Linux 程序通訊之記憶體對映Memory Map

一、簡介 正如其名(Memory Map),mmap 可以將某個裝置或者檔案對映到應用程序的記憶體空間中。通過直接的記憶體操作即可完成對裝置或檔案的讀寫。. 通過對映同一塊實體記憶體,來實現共享記憶體,完成程序間的通訊。由於減少了資料複製的次數,一定程度上提高了程序間通訊的效率。

JVM heap記憶體分配1

隨筆。記錄各型別在堆記憶體中佔用記憶體空間大小的理解: 引用型別在堆中佔用4位元組, byte,boolean基本型別在堆中佔用1位元組, char,short基本型別在堆中佔用2位元組, int,float基本型別在堆中佔用4位元組, long,double基本型

Qt總結之十一記憶體洩漏彙總

一、簡介        Qt記憶體管理機制:Qt 在內部能夠維護物件的層次結構。對於可視元素,這種層次結構就是子元件與父元件的關係;對於非可視元素,則是一個物件與另一個物件的從屬關係。在 Qt 中,在 Qt 中,刪除父物

java.lang.OutOfMemoryError:PermGenspace 持久溢位解決方法

       前段時間進行專案遷移的時候,遇見java.lang.OutOfMemoryError:PermGenspace,在網上查閱了相關的資料以及解決方案,在這裡記錄下來,方便以後查閱。  java 堆記憶體分為兩塊,Permanent

資料結構實驗之括號匹配的運用

#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int i,j,n,l; char s[150],p[150]; while(gets(s)!=NULL)

Java JVM垃圾回收

gc 分為兩段:新生代和老年代gc垃圾回收機制對什麼進行回收通過物件不可達演算法,對物件進行回收。物件不可達:當一個物件沒有任何一條鏈路指向根節點,就認為這個物件是不可達的,gc就會去回收它。gc垃圾回收機制做了什麼事主要是對新生代進行清理物件,老年代整理記憶體。Java堆分

Guru of the Week 條款09記憶體管理上篇

  GotW #09 Memory Management - Part I 著者:Herb Sutter 翻譯:kingofark [宣告]:本文內容取自www.gotw.ca網站上的Guru of the Week欄目,其著作權歸原著者本人所有。譯者kingofark在未經

Java虛擬機器記憶體管理--垃圾收集器及記憶體分配策略

概述     Java記憶體執行時區域的各個部分,其中程式計數器、虛擬機器棧、本地方法棧3個區域隨執行緒而生,隨執行緒而滅;棧中的棧幀隨著方法的進入和退出而有條不紊地執行著出棧和入棧操作。每一個棧幀中分配多少記憶體基本上是在類結構確定下來時就已知的(儘管在執行期會由JIT編