JAVA記憶體區域與記憶體溢位異常

高階java架構師發表於2018-09-28

正文

一. 基本概念

在開始講解之前, 需要先明確關於  JVM  的一些基本概念

我們都知道,  Java  是一個跨平臺的語言,  Java  跨平臺的基本支撐其實就是  JVM  對作業系統底層細節的遮蔽, 相當於加了一箇中間層(計算機中的任何問題都可以加一箇中間層解決~),  Java  不再像  C/C++  等語言一樣直接翻譯為針對特殊平臺的機器碼, 而是翻譯為位元組碼, 也即是我們的  class  檔案, 下圖大概可以比較簡明的概括了~; 位元組碼就相當於  Java  世界中的彙編, 而  JVM  則不是跨平臺的, 只是不同平臺的  JVM  都能識別和執行標準格式的位元組碼檔案而已

JAVA記憶體區域與記憶體溢位異常

關於  JVM  執行  class  檔案, 我覺得下圖已經可以比較準確的表達了

JAVA記憶體區域與記憶體溢位異常

我們下面要講的就是  Runtime Data Area  部分

二. 執行時資料區

JVM  會在執行  Java  程式的時候把它所管理的記憶體劃分為若干個不同的資料區, 如下:

JAVA記憶體區域與記憶體溢位異常

2.1 程式計數器

執行緒私有

2.1.1 儲存資料型別

指向下一條需要執行的位元組碼指令; 如果執行緒正在執行一個  Java  方法, 該計數器記錄的是正在執行的虛擬機器位元組碼指令的地址; 如果正在執行  Native  方法, 該計數器值則為空(  Undefined  )

2.1.2 異常情況

該區域是是唯一一個在  Java  虛擬機器中沒有規定任何  OutOfMemoryError  情況的區域

2.2 Java虛擬機器棧

執行緒私有

2.2.1 儲存資料型別

描述  Java  方法執行的記憶體模型, 每個方法呼叫就對應著一個棧幀的入棧和出棧; 一個棧幀裡面儲存了區域性變數表, 運算元棧, 動態連結, 方法出口等資訊

區域性變數表儲存了編譯器可知的各種基本資料型別, 物件引用,  returnAddress  ; 區域性變數表的大小在編譯期間即可確定, 執行期間大小不變

2.2.2 異常情況

  1. StackOverflowError  : 執行緒請求棧深度大於虛擬機器允許深度

異常示例程式碼:

public class JavaVMStackSOF {    private int stackLength = 1;    public void stackLeak() {
        stackLength++;
        stackLeak();
    }    public static void main(String[] args) {
        JavaVMStackSOF sof = new JavaVMStackSOF();        try {
            sof.stackLeak();
        } catch (Throwable e) {
            System.out.println("Stack Length: " + sof.stackLength);            throw e;
        }
    }
}
  1. OutOfMemoryError  : 虛擬機器棧動態擴充套件時無法申請到足夠記憶體

異常示例程式碼:

public class JavaVMStackOOM {    private void dontStop() {        while (true) {
        }
    }    public void stackLeakByThread() {        while (true) {            new Thread(new Runnable() {                @Override
                public void run() {
                    dontStop();
                }
            }).start();
        }
    }    public static void main(String[] args) {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

注: 由於作業系統分配給每個程式的記憶體空間是有限制的, 所以如果是由於建立過多的執行緒導致記憶體溢位, 在不能減少執行緒數或者更換  64  位虛擬機器的情況下, 可以選擇透過減少最大堆和減少棧容量來換取更多的執行緒

2.3 本地方法棧

執行緒私有

2.3.1 儲存資料型別

和虛擬機器棧類似, 只是本地方法棧提供的是  Native  方法服務

2.3.2 異常情況

StackOverflowError  和  OutOfMemoryError

2.4 Java堆

  1. 執行緒共享

  2. 垃圾收集管理的主要區域

2.4.1 儲存資料型別

幾乎所有的物件例項都在這裡分配

2.4.2 異常情況

OutOfMemoryError

異常示例:

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

2.5 方法區

  1. 執行緒共享

  2. 該區域的垃圾回收目標主要是針對常量池的回收和對型別的解除安裝

2.5.1 儲存資料型別

儲存已被虛擬機器載入的類資訊, 常量, 靜態變數, 即使編譯器編譯後的程式碼等資料

2.5.2 執行時常量池

執行時常量池是方法區的一部分, 但是  JDK6  之後, 常量池被放入了堆中;

Class  檔案中也有常量池部分, 即編譯期生成的各種字面量和符號引用, 這部分將在類載入後進入方法區的執行時常量池中, 此外還會把翻譯出來的直接引用也儲存在執行時常量池中

執行時常量池相對於  Class  檔案常量池的另外一個最重要的特徵是具備動態性, 即執行期間也可以將新的常量放入池中, 比如  String  的  intern()  方法

String.intern()  作用是: 如果字串常量池中已經包含一個等於此  String  物件的字串, 則返回代表池中這個字串的  String  物件; 否則, 將此  String  物件包含的字串新增到常量池中, 並且返回此  String  物件的引用

同樣, 收方法區的限制, 當常量池無法再申請到記憶體時會丟擲  OutOfMemoryError

2.5.3 異常情況

OutOfMemoryError  : 方法區無法滿足記憶體分配需求

異常示例:

public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {        List<String> list = new ArrayList<>();        int i = 0;        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }
}

2.6 直接記憶體

直接記憶體不是虛擬機器執行時資料區的一部分, 但是也被頻繁使用, 如: 在  JDK1.4  中新加入了  NIO  類, 引入了一種基於通道(  Chanel  )和緩衝區(  Buffer  )的  I/O  方式, 它可以使用  Native 函式庫直接分配堆外記憶體, 然後透過一個儲存在  Java  堆中的  DirectByteBuffer  物件作為這塊記憶體的引用進行操作, 避免了在  Java  堆和  Native  堆中來回複製資料, 提高效能

同樣會產生  OutOfMemoryError

三. 常見問題

注: 下文摘自文末參考連結, 權作個人筆記~

  1. 程式執行永遠都是在棧中進行的,因而引數傳遞時, 只存在傳遞基本型別和物件引用的問題, 不會直接傳物件本身; 但是傳引用的錯覺是如何造成的呢? 在執行棧中, 基本型別和引用的處理是一樣的, 都是傳值, 所以, 如果是傳引用的方法呼叫, 也同時可以理解為“傳引用值”的傳值呼叫, 即引用的處理跟基本型別是完全一樣的; 但是當進入被呼叫方法時, 被傳遞的這個引用的值, 被程式解釋(或者查詢)到堆中的物件, 這個時候才對應到真正的物件; 如果此時進行修改; 修改的是引用對應的物件; 而不是引用本身; 即: 修改的是堆中的資料; 所以這個修改是可以保持的了

  2. 我產生的物件不多呀, 為什麼還會產生  OutOfMemory  ?

答: 你繼承層次忒多了,  Heap  中產生的物件是先產生父類, 然後才產生子類, 明白不?

  1. OutOfMemory  錯誤分幾種? 答: 分兩種, 分別是  OutOfMemoryError:java heap size  和  OutOfMemoryError: PermGen space  , 兩種都是記憶體溢位,  heap size  是說申請不到新的記憶體了, 這個很常見,檢查應用或調整堆記憶體大小;  PermGenspace  是因為永久儲存區滿了, 這個也很常見, 一般在熱釋出的環境中出現, 是因為每次釋出應用系統都不重啟, 久而久之永久儲存區中的死物件太多導致新物件無法申請記憶體, 一般重新啟動一下即可

  2. 為什麼不建議在程式中顯式的生命  System.gc()  ? 答: 因為顯式宣告是做堆記憶體全掃描, 也就是  Full GC  , 是需要停止所有的活動的(  Stop The World Collection  ), 你的應用能承受這個嗎?

如果你想學好JAVA這門技術,也想在IT行業拿高薪,可以參加我們的訓練營課程,選擇最適合自己的課程學習,技術大牛親授,8個月後,進入名企拿高薪。我們的課程內容有:Java工程化、高效能及分散式、高效能、深入淺出。高架構。效能調優、Spring,MyBatis,Netty原始碼分析和大資料等多個知識點。如果你想拿高薪的,想學習的,想就業前景好的,想跟別人競爭能取得優勢的,想進阿里面試但擔心面試不過的,你都可以來,q群號為:180705916 進群免費領取學習資料。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31553506/viewspace-2215099/,如需轉載,請註明出處,否則將追究法律責任。

相關文章