jdk1.7.0_79 java
這張圖我相信基本上對JVM有點接觸的都應該很熟悉,能夠說這是JVM入門的第一課。其中的「堆」和「虛擬機棧(棧)」更是耳熟能詳。下面將圍繞這張圖對JVM的運行時數據區作一個簡單介紹。程序員
程序計數器(Program Counter Register)數組
這和計算機操做系統中的程序計數器相似,在計算機操做系統中程序計數器表示這個進程要執行的下個指令的地址,對於JVM中的程序計數器能夠看作是當前線程所執行的字節碼的行號指示器,每一個線程都有一個程序計數器(這很好理解,每一個線程都有在執行任務,若是線程切換後要能保證能恢復到正確的位置),重要的一點——程序計數器,這是JVM規範中惟一一個沒有規定會致使OutOfMemory(內存泄露,下文簡稱OOM)的區域。換句話上圖中的其他4個區域,都有可能致使OOM。jvm
☆虛擬機棧(Java Virtual Machine Stacks)spa
這塊內存區域就是咱們經常說的「棧」,咱們所熟知的是它用於存放變量,也就是說例如:操作系統
int i = 0;
虛擬機棧內存就會用4個字節來存儲i變量。對於變量的內存空間是一開始就能肯定的(對於引用型變量,它固然存儲的就是一個地址引用,其大小也是固定),因此這塊內存區域在編譯器就可以肯定下來,這塊區域可能會拋出StackOverflowError或者OOM錯誤。設置JVM參數」-Xss228k」(棧大小爲228k)。線程
1 package com.jvm; 2 3 /** 4 * -Xss228k,虛擬機棧大小爲228k 5 * Created by yulinfeng on 7/11/17. 6 */ 7 public class Test { 8 private static int count = 0; 9 10 public static void main(String[] args) { 11 Test test = new Test(); 12 test.test(); 13 } 14 15 /** 16 * 遞歸調用 17 */ 18 private void test() { 19 try { 20 count++; 21 test(); 22 } catch (Throwable e) { //Exception已經捕獲不了JVM拋出的StackOverflowError 23 System.out.println("遞歸調用次數" + count); 24 e.printStackTrace(); 25 } 26 } 27 }
這是一段沒有終止條件的遞歸,執行結果以下圖所示,JVM拋出StackOverflowError表示線程請求的棧深度大於JVM所容許的深度。3d
對於單線程狀況下,不管如何拋出的都是StackOverflowError。若是要拋出OOM異常,致使的緣由是不斷地在建立線程,直到將內存消耗殆盡。code
JVM的內存由堆內存 + 方法區內存 + 剩餘內存,也就是剩餘內存=操做系統分配給JVM的內存 - 堆內存 - 方法區內存。-Xss設置的是每一個線程的棧容量,也就是說能夠建立的線程數量 = 剩餘內存 / 棧內存。此時若是棧內存越大,能夠建立的線程數量就少,就容易出現OOM;若是棧內存越小,能夠建立的線程數量就多,就不容易出現OOM。對象
要避免這種狀況最好就是減小堆內存+方法區內存,或者適當減小棧內存。對於棧內存的配置,通常採用默認值1M,或者採用64位操做系統以及64位的JVM。
本地方法棧(Native Method Stack)
本地方法棧和虛擬機棧相似,不一樣的是虛擬機棧服務的是Java方法,而本地方法棧服務的是Native方法。在HotSpot虛擬機實現中是把本地方法棧和虛擬機棧合二爲一的,同理它也會拋出StackOverflowError和OOM異常。
☆Java堆(Java Heap)
對於堆,Java程序員都知道對象實例以及數組內存都要在堆上分配。堆再也不被線程所獨有而是共享的一塊區域,它的確是用來存放對象實例,也是垃圾回收GC的主要區域。實際上它還能細分爲:新生代(Young Generation)、老年代(Old Generation)。對於新生代又分爲Eden空間、From Survivor空間、To Survivor空間。至於爲何這麼分,這涉及JVM的垃圾回收機制,在這裏不作敘述。堆一樣會拋出OOM異常,下面例子設置JVM參數」 -Xms20M -Xmx20M「(前者表示初始堆大小20M,後者表示最大堆大小20M)。
1 package com.jvm; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * -Xms20M -Xmx20M 堆初始大小20M 堆最大大小20M 8 * Created by yulinfeng on 7/11/17. 9 */ 10 public class Test { 11 12 public static void main(String[] args) { 13 List<Test> list = new ArrayList<Test>(); 14 int count = 0; 15 try { 16 while (true) { 17 count++; 18 list.add(new Test()); //不斷建立線程 19 } 20 } catch (Throwable e) { 21 System.out.println("建立實例個數:" + count); 22 e.printStackTrace(); 23 } 24 25 } 26 }
執行的結果能夠清楚地看到堆上的內存空間溢出了。
☆方法區(Method Area)
對於JVM的方法區,可能聽得最多的是另一個說法——永久代(Permanent Generation),呼應堆的新生代和老年代。方法區和堆的劃分是JVM規範的定義,而不一樣虛擬機有不一樣實現,對於Hotspot虛擬機來講,將方法區歸入GC管理範圍,這樣就沒必要單獨管理方法區的內存,因此就有了」永久代「這麼一說。方法區和操做系統進程的正文段(Text Segment)的做用很是相似,它存儲的是已被虛擬機加載的類信息、常量(從JDK7開始已經移至堆內存中)、靜態變量等數據。現設置JVM參數爲」-XX:MaxPermSize=20M」(方法區最大內存爲20M)。
1 package com.jvm; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * -XX:MaxPermSize=20M 方法區最大大小20M 8 * Created by yulinfeng on 7/11/17. 9 */ 10 public class Test { 11 12 public static void main(String[] args) { 13 List<String> list = new ArrayList<String>(); 14 int i = 0; 15 while (true) { 16 list.add(String.valueOf(i++).intern()); //不斷建立線程 17 } 18 } 19 }
實際上對於以上代碼,在JDK6、JDK7、JDK8運行結果均不同。緣由就在於字符串常量池在JDK6的時候仍是存放在方法區(永久代)因此它會拋出OutOfMemoryError:Permanent Space;而JDK7後則將字符串常量池移到了Java堆中,上面的代碼不會拋出OOM,若將堆內存改成20M則會拋出OutOfMemoryError:Java heap space;至於JDK8則是純粹取消了方法區這個概念,取而代之的是」元空間(Metaspace)「,因此在JDK8中虛擬機參數」-XX:MaxPermSize」也就沒有了任何意義,取代它的是」-XX:MetaspaceSize「和」-XX:MaxMetaspaceSize」等。