運行時數據區介紹

其中線程共享區域:方法區 、 堆java

線程私有區域:虛擬機棧、本地方法棧、程序計數器程序員

程序計數器

程序計數器是一塊很是小的內存空間,主要是用來對當前線程所執行的字節碼的行號指示器;
並且程序計數器是內存區域中惟一一塊不存在OutOfMemoryError的區域

虛擬機棧

程序員常常說「堆棧」,其中的棧就是虛擬機棧,更確切的說,你們談的棧是虛擬機中的局部變量表部分;數組

虛擬機棧描述的是:Java方法執行的內存模型;(說白了就是:緩存

虛擬機棧就是用來存儲:局部變量、操做棧、動態鏈表、方法出口這些東西;服務器

這些東西有個特色:都是線程私有的,因此虛擬機棧是線程私有的併發

由於虛擬機棧是私有的,當線程調用某一個方法再到這個方法執行結束;其實就是對應着一個棧幀在虛擬機入棧到出棧的過程;框架

對於虛擬機棧可能出現的異常有兩種socket

1:若是線程請求的棧深度大於虛擬機棧容許的最大深度,報錯:StackOverflowError
(這種錯誤常常出如今遞歸操做中,無限制的反覆調用方法,最終致使壓棧深度超過虛擬機容許的最大深度,就會報錯)

2:java的虛擬機棧能夠進行動態擴展,但隨着擴展會不斷的申請內存,當沒法申請足夠內存的時候就會報錯:OutOfMemoryError

本地方法棧

本地方法棧(Native Method Stacks)與虛擬機棧所發揮的做用是很是類似的,其區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native方法服務(好比C語言寫的程序和C++寫的程序)

Java堆

Java堆是Java虛擬機管理內存最大的一塊區域;而且Java堆是被全部線程共享的一塊內存區域(最大的區域);函數

對於堆內存惟一的目的就是用來存放對象實例的,並且幾乎全部的對象實例都是堆內存中分配的內存(可能會有一些對象逃逸分析技術會致使對象實例不是在Java堆中進行內存分配的)高併發

常常會聽到一些程序說「調優」,其中調優的95%部分都是在跟Java堆有關係的;

由於Java堆是垃圾收集器管理的主要區域

堆是JVM管理的內存最大的一塊,隨着虛擬機的啓動而建立的。全部數組的內存和new出來的對象都是從這裏分配的內存。簡單說就是程序員的代碼可觸及的地方.

並且一個JVM實例運行的時候只有一個Heap區域,這個區域會被全部的線程共享

堆裏面又分了區域

1、新生代
二、老年代

新生代

剛建立的對象都是在新生代的,而後垃圾回收的線程掃一遍,若是是垃圾就回收,不是垃圾就年齡+1,新生代和老年代的觸發點是能夠人爲配置的

老年代

新生代的對象進入老年代之後,仍然須要垃圾回收的線程掃描,若是發現垃圾了就回收,若是沒有垃圾的話,就年齡+1. 最後對象在無用後會被GC掉

調整參數:

-Xms: 2G

-Xmx:2G

-XX:NewSize和-XX:MaxNewSize用於設置年輕代的大小,建議設爲整個堆大小的1/3或者1/4,兩個值設爲同樣大。
-XX:SurvivorRatio 用來控制Eden和survivor Ratio比例
-Xms:JVM初始分配的內存由-xms決定。系統默認給-xms的大小是服務器物理內存的1/64
-Xmx:JVM最大分配的內存由-xmx決定。系統默認給-xmx的大小是服務器物理內存的1/4

注意:

當咱們服務器的空餘堆內存小於40%的時候,JVM就會增大堆內存,一直增長到-xmx限定的內存大小。
當咱們的服務器空餘內存大於70%的時候,,JVM就會減少堆內存,直到達到-xms限制的內存大小。
優化:所以,咱們通常都是把JVM的-xms和-xmx都設置成相等。避免每次GC夠調整堆的大小,形成內存抖動。

方法區

方法區和java堆同樣,是線程共享的區域;

方法區的做用的就是用來存儲:已經被虛擬機加載的類信息、常量、靜態變量等

並且方法區還有另外一種叫法:【非堆】,也有人給方法區叫作永久代

當方法區存儲信息過大時候,也就是沒法知足內存分配的時候報錯

 

運行時常量池

運行時常量池是方法區中的一部分,主要是用來存放程序編譯期生成的各類字面量和符號引用,也就是在類加載以後會進入方法區的運行時常量池中存放

 

直接內存

直接內存(Direct Memory)並非運行時數據區中的部分;可是這塊兒每每會被大多數程序員忽略,不當心也會致使OOM的錯誤;

這是由於在JDK1.4以前java操做數據過程當中使用的IO操做是原始的socket IO

傳統的IO,經過socket的方式發送給服務端,須要幹些什麼事情:

1、先把文件讀到操做系統的緩存當中
2、再把操做系統的緩存中內容讀到應用中
3、再從應用的緩存當中讀到發送的socket緩存當中
四、在從socket緩存當中把數據發出去

總共作了4次的拷貝:

NIO:

NIO比較傳統IO的話,系統中的buffer再也不須要拷貝給應用了​而是read buffer 直接拷貝給socket buffer​咱們的應用只須要在兩個buffer之間創建一個管道的​這樣省略了兩次的copy。速度就快了不少

NIO能夠直接使用Native(本地方法棧)函數庫直接分配堆外內存,而後經過一個存儲在Java堆中的DirectByteBuffer對象做爲堆外內存的引用進行操做;

因此有時候程序員在分配內存時候常常會忽略掉直接內存。致使各個區域的內存總和大於物理內存限制,而後OOM

常見的:OutOfMemeoryError

Java堆溢出

Vm Options添加:

-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError 出現OOM錯誤的時候產生堆棧日誌:

使用MAT分析堆棧日誌:

打開堆內存的樹信息:

點開樹結構:

能夠看到出現OOM是由於建立了太多的OOM_demo這個類形成的,這樣經過代碼查找就能找到問題所在的根源了;

注意:大數據框架的堆棧信息也是經過以上方法搞定的;

虛擬機棧和本地方法棧溢出

Java虛擬機規範中描述了兩種異常:

1:若是線程請求的棧深度大於虛擬機容許的最大深度,則報錯:StackOverFlowError

2:若是虛擬機在擴展時沒法申請到足夠的內存空間,則拋出OutOfMemoryError

StackOverFlowError

-Xss128K

public class StackOverFlow {
    public static void main(String[] args) {
        new StackOverFlow().pushStack();
    }
    int index = 0;
    public void pushStack(){
        System.out.println("壓棧第 :  "+index++);
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread().getState();
        pushStack();
    }
}

stackOOM

-Xss2m

public class Stack_OOM {
    public static volatile int index = 0;
    public static void main(String[] args) {
        new Stack_OOM().addThread();
    }

    public void dont_stop(){
        while (true){

        }
    }

    public void addThread(){
        while (true){
            Thread thread = new Thread(new Runnable()
            {
                public void run()
                {
                    System.out.println(index++);
                    dont_stop();
                }
            });
            thread.start();
        }
    }
}

public class Stack_OOM {
    public static volatile int index = 0;
    public static void main(String[] args) {
        new Stack_OOM().addThread();
    }

    public void dont_stop(){
        while (true){

        }
    }

    public void addThread(){
        while (true){
            Thread thread = new Thread(new Runnable()
            {

                public void run()
                {
                    System.out.println(index++);
                    dont_stop();
                }
            });
            thread.start();

        }
    }
}

每一個方法在執行的時候都會建立一個棧幀,這個棧幀伴隨着方法從建立到執行完成;

對於虛擬機來講:總內存 –Xmx - MaxPermSize-程序計數器消耗內存(能夠忽略) = 虛擬機棧+本地方法棧

也就意味着說,若是每一個線程分配的內存越大,能夠建立的線程數就很是少,創建線程的時候就特別容易把內存吃光;

因此在出現建立過多的線程致使OOM的時候,咱們能夠減小最大堆外內存或者減小棧容量(-Xss)來提升線程的數量;這樣在高併發狀況下能夠解決由於建立過多的線程致使的OOM

方法區和運行時常量池溢出:

在方法區中有個小塊區域叫作「常量池」。

例子1:

String a = "123";
String b = "123";
System.out.println(a == b);

上面代碼的結果爲true,由於String + 變量 是將變量放在常量池的;

==操做比較的是兩個變量的值是否相等

對於引用型變量表示的是兩個變量在堆中存儲的地址是否相同,即棧中的內容是否相同。

equals操做表示的兩個變量是不是對同一個對象的引用,即堆中的內容是否相同

例子2:

String aa = new String("123");
String bb = new String("123");
System.out.println(aa == bb);

上面代碼的結果爲false;

經過new生成的對象是放在堆區的,使用==比較對象的地址,因此結果爲false;

例子3:

String c = "c";
String cc = new String("c");
System.out.println(c == cc);

上面的列子中String c = "c";是將變量值存放在常量池的;

String cc = new String("c");是將對象存放在堆中的,因此兩個在==號下是false

例子4:

String d = "d";
String dd = new String("d").intern();
System.out.println(d == dd);
 
 

String d = "d";是將值存儲在常量池的

 
 

String dd = new String("d")是將值存儲在對象中的

 
 

可是使用intern()方法以後,d == dd就變成true了

 
 

當調用intern()方法時,無論使用什麼方式定義一個字符串,都會首先在常量池中查找是否有相應的字符串存在,若是有,直接返回常量池中的引用,不然,在常量池中生成相應的字符串並返回引用

 
 

因此每每在生產上會在這方面作一些優化:



由於使用intern()比不使用intern()消耗的內存更少; 注意:使用intern()方法後程序運行時間有所增長,這是由於程序中每次都是用了new String後又進行intern()操做的耗時時間,可是不使用intern()佔用內存空間致使GC的時間是要遠遠大於這點時間的

但是若是有很是多的值經過intern存儲到常量池中的時候,常量池的空間或者說方法區的空間沒法在繼續添加值的時候,就會報錯:OutOfMemoryError:PermGen space

例子:

VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M

List<String> list = new ArrayList<String>();
int i=0;
while (true){
    list.add(String.valueOf(i).intern());
}

注意:在JDK1.8之後,正式移除了永久代!,取而代之的是【元空間】

元空間的本質和永久代相似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存[因此元空間Metaspace仍然在非堆中]。所以,默認狀況下,元空間的大小僅受本地內存限制,但能夠經過如下參數來指定元空間的大小:

-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:若是釋放了大量的空間,就適當下降該值;若是釋放了不多的空間,那麼在不超過MaxMetaspaceSize時,適當提升該值。 
 
-XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。

除了上面兩個指定大小的選項之外,還有兩個與 GC 相關的屬性:

-XX:MinMetaspaceFreeRatio,在GC以後,最小的Metaspace剩餘空間容量的百分比,減小爲分配空間所致使的垃圾收集 
-XX:MaxMetaspaceFreeRatio,在GC以後,最大的Metaspace剩餘空間容量的百分比,減小爲釋放空間所致使的垃圾收集

因此上面的VM Args的參數稍微修改:

-XX:MetaspaceSize=2m -XX:MaxMetaspaceSize=2m

public class Demo {
    public static void main(String[] args) {
        int index = 1;
        List<String> list = new ArrayList<String>();
        while (true){
            String str = "寫個長一點的字符串好報錯" + index++;
            list.add(str.intern());
        }
    }
}

報錯:

Error occurred during initialization of VM

OutOfMemoryError: Metaspace

直接內存溢出

當程序中使用NIO存儲數據,存儲的數據容量超過了本地方法棧容許的容量的時候,就會報錯: java.lang.OutOfMemoryError: Direct buffer memory

-Xmx 20m -XX:MaxDirectMemorySize=10m -XX:+PrintGCDetails

代碼:

public class DirectMemOOM {
    private static final int _1m = 1024*1024;
    public static void main(String[] args) throws IllegalAccessException {
        ByteBuffer.allocateDirect(11*_1m);
    }
}
相關文章
相關標籤/搜索